Load libraries

library(here)
library(readr)
library(tidyr)
library(dplyr)
library(lubridate)
library(ggplot2)
library(stringr)
library(purrr)
library(readxl)

Read the data

db_resurv<-read_tsv(here("data", "edited", "db_resurv.csv"))
Aviso: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 425310 Columns: 69── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (31): Country, Biblioreference, Nr. table in publ., Cover abundance scal...
dbl (22): PlotObservationID, PlotID, TV2 relevé number, Nr. relevé in table,...
lgl (16): Lon1, Lon2, Lon3, Lon4, Lat1, Lat2, Lat3, Lat4, X1, X2, X3, X4, Y1...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Problems (do not affect us)

problems<-problems(db_resurv)
sort(unique(problems$col))
[1]  7 13
names(db_resurv[c(7,13)])
[1] "Nr. relevé in table" "Altitude"           

No problems really!

Update coordinates

Create new column with old coordinates if new not available, and with new if available.

db_resurv <- db_resurv %>%
  mutate(Lon_updated = ifelse(is.na(Lon_prec),Longitude,Lon_prec),
         Lat_updated = ifelse(is.na(Lat_prec),Latitude,Lat_prec))
print(db_resurv, width = Inf)

ISSUE 1: ReSurvey plot is NA

Careful! Sometimes ReSurvey plot is NA.

nrow(db_resurv%>%filter(is.na(RS_CODE)))
[1] 0
nrow(db_resurv%>%filter(is.na(`ReSurvey site`)))
[1] 0
nrow(db_resurv%>%filter(is.na(`ReSurvey plot`)))
[1] 111
write_csv(db_resurv %>% filter(is.na(`ReSurvey plot`)),
          here("output", "csv","issue1.csv"))

ISSUE 2: Coordinates are NA

Coordinates are also NA in some cases.

nrow(db_resurv%>%filter(is.na(Lon_updated) & is.na(Lat_updated )))
[1] 949
write_csv(db_resurv %>% filter(is.na(Lon_updated) & is.na(Lat_updated)),
          here("output", "csv","issue2.csv"))

But Resurvey plot and coordinates are never missing at the same time.

nrow(db_resurv%>%
       filter(is.na(`ReSurvey plot`) & is.na(Lon_updated) & is.na(Lat_updated)))
[1] 0

ISSUE 3: Different ReSurvey observations within the same plot have different coordinates

Careful! In some cases different ReSurvey observations within the same plot have different coordinates.

Create two new columns in db_resurv: coordinates_equal indicating if coordinates are exactly equal between ReSurvey observations, and coordinates_consistent, indicating if coordinates are consistent between ReSurvey observations (consistent meaning that difference < 0.001 degrees).

# Define a threshold (e.g., 0.001 degrees for longitude/latitude differences)
threshold <- 0.001

db_resurv <- db_resurv %>%
  group_by(RS_CODE, `ReSurvey site`, `ReSurvey plot`) %>%
  mutate(
    lon_range = ifelse(all(is.na(Lon_updated)), NA,
                        max(Lon_updated, na.rm = T) - 
                         min(Lon_updated, na.rm = T)),
    lat_range = ifelse(all(is.na(Lat_updated)), NA,
                        max(Lat_updated, na.rm = T) - 
                         min(Lat_updated, na.rm = T)),
    coordinates_equal = ifelse(is.na(Lon_updated) & is.na(Lat_updated), NA,
                               lon_range == 0 & lat_range == 0),
    coordinates_consistent = ifelse(is.na(Lon_updated) & is.na(Lat_updated), NA,
                                    lon_range < threshold & 
                                      lat_range < threshold)
  ) %>%
  ungroup() %>%
  select(-lon_range, -lat_range)
write_csv(db_resurv %>% filter(coordinates_equal==FALSE),
          here("output", "csv","issue3.csv"))
db_resurv %>% 
  group_by(RS_CODE,`ReSurvey site`, `ReSurvey plot`) %>%
  summarize(is_equal = all(coordinates_equal),
            is_consistent = all(coordinates_consistent),
            .groups = "drop") %>%
  mutate(coordinate_status = case_when(
    is_equal ~ "Equal",
    !is_equal & is_consistent ~ "Consistent (< 0.001º)",
    !is_equal & !is_consistent ~ "Inconsistent (> 0.001º)")) %>%
  count(coordinate_status)%>%
  mutate(percentage = n / sum(n) * 100) %>%
  ggplot(aes(x = percentage, y = coordinate_status, fill = coordinate_status)) +
  geom_bar(stat = "identity") + 
  geom_text(aes(label = paste0(round(percentage, 1), "%")),
            position = position_stack(vjust = 0.5), size = 3) + 
  labs(x = "Percentage of Plots", y = NULL) +
  theme(axis.text.y = element_text(size = 12)) +
  coord_flip() + theme(legend.position = "none")
ggsave(filename=here("output", "figures","issue3.tiff"),
       width=10,height=7,units="cm",dpi=300)

ISSUE 4: Some plots have only one resurvey

When ReSurvey plot is not NA, use the unique combination of RS_CODE, ReSurvey site and ReSurvey plot to uniquely define each ReSurvey plot (as defined in metadata). When ReSurvey plot is NA (111 rows), use the unique combination of RS_CODE, ReSurvey site and updated coordinates to uniquely define each ReSurvey plot. Check how many resurveys (i.e. different years are there for each unique combination).

count_resurveys <- db_resurv %>%
  # Convert dates to date format and get the year
  mutate(date = dmy(`Date of recording`), year = year(date)) %>%
  group_by(RS_CODE, `ReSurvey site`,
           # If ReSurvey plot is not NA, 
           # group by RS_CODE, `ReSurvey site`, `ReSurvey plot`
           `ReSurvey plot` = ifelse(is.na(`ReSurvey plot`), 
                                    NA_character_, `ReSurvey plot`),
           # If ReSurvey plot is NA, group by coordinates
           Lon_updated = ifelse(is.na(`ReSurvey plot`), Lon_updated, NA_real_),
           Lat_updated = ifelse(is.na(`ReSurvey plot`) , Lat_updated, NA_real_)
  ) %>%
  summarise(
    # Get how many different years for each unique group
    distinct_years=n_distinct(year), 
    # Get how many different dates for each unique group
    distinct_dates=n_distinct(date), .groups = "drop")

Summary stats:

summary(count_resurveys$distinct_years)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   2.000   2.000   3.449   4.000  55.000 
sd(count_resurveys$distinct_years)
[1] 2.677649

Histograms:

# For all data
ggplot(count_resurveys, aes(x = distinct_years)) + 
  geom_histogram(fill = "white", color = "black", bins = 55)+
  xlab("Number of ReSurvey observations (different years)") +
  ylab("Number of plots")
ggsave(filename=here("output", "figures","issue4.tiff"),
       width=11,height=7,units="cm",dpi=300)

Number and proportion of plots with only 1 resurvey (should not be so!)

nrow(count_resurveys%>%filter(distinct_years==1))
[1] 1158
nrow(count_resurveys%>%filter(distinct_years==1))/nrow(count_resurveys)
[1] 0.009573571
write_csv(count_resurveys%>%filter(distinct_years==1),
          here("output", "csv","issue4.csv"))

ISSUE 5: Datasets with only presence/absence

db_resurv %>%
  filter(`Cover abundance scale`=="Presence/Absence") %>%
  distinct(Dataset)
ggplot(db_resurv %>% 
         mutate(pres_or_ab =ifelse(`Cover abundance scale`=="Presence/Absence",
                                   "Presence/Absence", "Abundance"),
                DK_Naturdata_Res = ifelse(Dataset == "DK_Naturdata_Res",
                                          "Y", "N")),
                aes(pres_or_ab, fill = DK_Naturdata_Res)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage", x = NULL)
ggsave(filename=here("output", "figures","issue5.tiff"),
       width=12,height=7,units="cm",dpi=300)

For DK_Naturdata_Res - info about habitat from Jerker’s file (see below).

ISSUE 6: Observations with wrong country (GIS)

Read text file wrong_countries obtained in ArcGIS:

wrong_countries <- read_delim(here("data", "clean","wrong_countries.txt"),
                              delim = ";")
Rows: 1850 Columns: 19── Column specification ────────────────────────────────────────────────────────
Delimiter: ";"
chr (12): Country, RS_CODE, ReSurvey_s, ReSurvey_p, Lon_update, Lat_update, ...
dbl  (7): FID, Join_Count, TARGET_FID, plot_uniqu, year, obs_unique, PlotObsID
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
write_csv(wrong_countries, here("output", "csv","issue6.csv"))

ISSUE 7: Different cover abundance scales

ggplot(db_resurv, aes(`Cover abundance scale`)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations", x = "Cover abundance scale") +
  coord_flip()
ggsave(filename=here("output", "figures","issue7.tiff"),
       width=18,height=10,units="cm",dpi=300)

ISSUE 8: Wrong EUNIS codes

Used this info in metadata file:

Expert system classification to EUNIS habitats (https://zenodo.org/records/4812736 ; https://floraveg.eu/habitat/). I am sending you legend for EUNIS classification version 2022-10-16 with all codes and meanings, directly prepared from expert system file (second sheet) - it is slightly different from published version in ZENODO (https://zenodo.org/records/4812736 , little bit old dated now) and from https://floraveg.eu/habitat/ (little bit newer than in current EVA version).

Qa = mires and Qb = wetlands P units – in floraveg.eu there is slightly different classification (https://floraveg.eu/habitat/overview/P), but in EVA is still this classification of P:

P Surface waters Pa Base-poor spring and spring brook Pb Calcareous spring and spring brook Pc Brackish-water vegetation Pd Fresh-water small pleustophyte vegetation Pe Fresh-water large pleustophyte vegetation Pf Fresh-water submerged vegetation Pg Fresh-water nymphaeid vegetation Ph Oligotrophic-water vegetation Pi Dystrophic-water vegetation Pj Stonewort vegetation

Presence of “!” simply means that for one unit there are two or more different formulas, e.g. R11 and R11!. So it is only technical stuff.

Multiple assignment of relevé – no priority, alphabetical order, e.g. N16!,S66,S81 means that relevé can be assigned to all 3 units: N16 Mediterranean and Macaronesian coastal dune grassland (grey dune), S66 Mediterranean halo-nitrophilous scrub and S81 Canarian xerophytic scrub

No value present in Expert System – relevé didn´t enter expert system classification (= it means that some prerequisites are missing)

“~” – relevé entered expert classification however was not classified to any EUNIS unit +

Clean info on Expert system column and separate it when there are several codes.

db_resurv <- db_resurv %>%
  mutate(
    # Clean 'Expert System' column by removing "!" and replacing "~" with NA
    `Expert System` = case_when(
      `Expert System` == "~" ~ NA_character_,  # Replace "~" with NA
      TRUE ~ str_replace_all(`Expert System`, "!", "")  # Remove "!"
    )
  ) %>%
  # Separate the values in 'Expert System' into multiple columns
  separate(
    `Expert System`,
    into = c("EUNISa", "EUNISb", "EUNISc", "EUNISd"),
    sep = ",",
    extra = "drop",  # Drop extra values if there are more than columns
    fill = "right",   # Fill missing values with NA for cases with fewer values
    remove = FALSE    # Keep the original 'Expert System' column
  )

Calculate how many different EUNIS codes have been assigned:

db_resurv <- db_resurv %>%
  mutate(
    # Count the number of non-NA values across the EUNIS columns
    n_EUNIS = rowSums(!is.na(select(., starts_with("EUNIS"))))
  )
ggplot(db_resurv, aes(n_EUNIS)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "Number of differnt EUNIS codes assigned") + coord_flip()

ggplot(db_resurv %>% filter(n_EUNIS > 0), aes(n_EUNIS)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "Number of differnt EUNIS codes assigned") + coord_flip()

Correct some EUNIS codes that are probably wrong:

db_resurv <- db_resurv %>%
  mutate(across(starts_with("EUNIS"), ~ case_when(
    . == "N16M" ~ "N16",
    . == "Sa" ~ "V4",
    . == "Sb" ~ "V5",
    . == "T1CT" ~ "T1C",
    . == "N15A" ~ "N15",
    TRUE ~ .
  )))

Add columns for the different EUNIS levels:

db_resurv <- db_resurv %>%
  mutate(
    # EUNISa levels
    EUNISa_1 = substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 2, 1)),
    EUNISa_2 = ifelse(
      nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 3, 2), 
      substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 3, 2)),
      NA_character_
    ),
    EUNISa_3 = ifelse(
      nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 4, 3), 
      substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 4, 3)),
      NA_character_
      ),
    EUNISa_4 = ifelse(
      nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 5, 4), 
      substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 5, 4)),
      NA_character_
    ),
    
    # EUNISb levels
    EUNISb_1 = substr(EUNISb, 1, ifelse(str_starts(EUNISb, "MA"), 2, 1)),
    EUNISb_2 = ifelse(
      nchar(EUNISb) >= ifelse(str_starts(EUNISb, "MA"), 3, 2), 
      substr(EUNISb, 1, ifelse(str_starts(EUNISb, "MA"), 3, 2)),
      NA_character_
    ),
    EUNISb_3 = ifelse(
      nchar(EUNISb) >= ifelse(str_starts(EUNISb, "MA"), 4, 3), 
      substr(EUNISb, 1, ifelse(str_starts(EUNISb, "MA"), 4, 3)),
      NA_character_
    ),
    EUNISb_4 = ifelse(
      nchar(EUNISb) >= ifelse(str_starts(EUNISb, "MA"), 5, 4), 
      substr(EUNISb, 1, ifelse(str_starts(EUNISb, "MA"), 5, 4)),
      NA_character_
    ),
    
    # EUNISc levels
    EUNISc_1 = substr(EUNISc, 1, ifelse(str_starts(EUNISc, "MA"), 2, 1)),
    EUNISc_2 = ifelse(
      nchar(EUNISc) >= ifelse(str_starts(EUNISc, "MA"), 3, 2), 
      substr(EUNISc, 1, ifelse(str_starts(EUNISc, "MA"), 3, 2)),
      NA_character_
    ),
    EUNISc_3 = ifelse(
      nchar(EUNISc) >= ifelse(str_starts(EUNISc, "MA"), 4, 3), 
      substr(EUNISc, 1, ifelse(str_starts(EUNISc, "MA"), 4, 3)),
      NA_character_
    ),
    EUNISc_4 = ifelse(
      nchar(EUNISc) >= ifelse(str_starts(EUNISc, "MA"), 5, 4), 
      substr(EUNISc, 1, ifelse(str_starts(EUNISc, "MA"), 5, 4)),
      NA_character_
    ),
    
    # EUNISd levels
    EUNISd_1 = substr(EUNISd, 1, ifelse(str_starts(EUNISc, "MA"), 2, 1)),
    EUNISd_2 = ifelse(
      nchar(EUNISd) >= ifelse(str_starts(EUNISd, "MA"), 3, 2), 
      substr(EUNISd, 1, ifelse(str_starts(EUNISd, "MA"), 3, 2)),
      NA_character_
    ),
    EUNISd_3 = ifelse(
      nchar(EUNISd) >= ifelse(str_starts(EUNISd, "MA"), 4, 3), 
      substr(EUNISd, 1, ifelse(str_starts(EUNISd, "MA"), 4, 3)),
      NA_character_
    ),
    EUNISd_4 = ifelse(
      nchar(EUNISd) >= ifelse(str_starts(EUNISd, "MA"), 5, 4), 
      substr(EUNISd, 1, ifelse(str_starts(EUNISd, "MA"), 5, 4)),
      NA_character_
    )
  )

Create new columns with descriptions for the level 1 codes:

db_resurv <- db_resurv %>%
  mutate(
    EUNISa_1_descr = case_when(
      EUNISa_1 == "V" ~ "Vegetated man-made habitats",
      EUNISa_1 == "U" ~ "Inland habitats with no or little soil",
      EUNISa_1 == "T" ~ "Forests and other wooded land",
      EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
      EUNISa_1 == "R" ~ "Grasslands",
      EUNISa_1 == "Q" ~ "Wetlands",
      EUNISa_1 == "P" ~ "Inland waters",
      EUNISa_1 == "N" ~ "Coastal habitats",
      EUNISa_1 == "MA" ~ "Marine habitats",
      TRUE ~ NA_character_
    ),
    EUNISb_1_descr = case_when(
      EUNISb_1 == "V" ~ "Vegetated man-made habitats",
      EUNISb_1 == "U" ~ "Inland habitats with no or little soil",
      EUNISb_1 == "T" ~ "Forests and other wooded land",
      EUNISb_1 == "S" ~ "Heathlands, scrub and tundra",
      EUNISb_1 == "R" ~ "Grasslands",
      EUNISb_1 == "Q" ~ "Wetlands",
      EUNISb_1 == "P" ~ "Inland waters",
      EUNISb_1 == "N" ~ "Coastal habitats",
      EUNISb_1 == "MA" ~ "Marine habitats",
      TRUE ~ NA_character_
    ),
    EUNISc_1_descr = case_when(
      EUNISc_1 == "V" ~ "Vegetated man-made habitats",
      EUNISc_1 == "U" ~ "Inland habitats with no or little soil",
      EUNISc_1 == "T" ~ "Forests and other wooded land",
      EUNISc_1 == "S" ~ "Heathlands, scrub and tundra",
      EUNISc_1 == "R" ~ "Grasslands",
      EUNISc_1 == "Q" ~ "Wetlands",
      EUNISc_1 == "P" ~ "Inland waters",
      EUNISc_1 == "N" ~ "Coastal habitats",
      EUNISc_1 == "MA" ~ "Marine habitats",
      TRUE ~ NA_character_
    ),
    EUNISd_1_descr = case_when(
      EUNISd_1 == "V" ~ "Vegetated man-made habitats",
      EUNISd_1 == "U" ~ "Inland habitats with no or little soil",
      EUNISd_1 == "T" ~ "Forests and other wooded land",
      EUNISd_1 == "S" ~ "Heathlands, scrub and tundra",
      EUNISd_1 == "R" ~ "Grasslands",
      EUNISd_1 == "Q" ~ "Wetlands",
      EUNISd_1 == "P" ~ "Inland waters",
      EUNISd_1 == "N" ~ "Coastal habitats",
      EUNISd_1 == "MA" ~ "Marine habitats",
      TRUE ~ NA_character_
    )
  )

Plot for EUNISa_1 (the first assigned EUNIS in cases of multiple assignations, level 1):

ggplot(db_resurv, aes(EUNISa_1_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 1") + coord_flip()

ggplot(db_resurv %>% filter(!is.na(EUNISa_1_descr)), aes(EUNISa_1_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 1") + coord_flip()
ggsave(filename=here("output", "figures","issue8.tiff"),
       width=18,height=10,units="cm",dpi=300)

ISSUE 9: Manipulated plots and info on manipulation type

ggplot(db_resurv, aes(`Manipulate (y/n)`)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "Manipulation")
ggsave(filename=here("output", "figures","issue9.tiff"),
       width=10,height=8,units="cm",dpi=300)

List of Type of Manipulation in manipulated plots (mixed information):

write_csv(data.frame(unique(db_resurv$`Type of manipulation`)),
          here("output", "csv","issue9.csv"))

ISSUE 10: Location method

ggplot(db_resurv, aes(`Location method`)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "Location method") + coord_flip()
ggsave(filename=here("output", "figures","issue10.tiff"),
       width=18,height=8,units="cm",dpi=300)

ISSUE 11: Resurvey project types

unique(db_resurv$RS_PROJTYP)
[1] "resampling"      "permanent"       "permanent (man)" "Permanent (man)"
[5] "Resampling"     

Unify codes:

db_resurv <- db_resurv %>%
  mutate(RS_PROJTYP = recode(RS_PROJTYP,
                             "Resampling" = "resampling",
                             "Permanent (man)" = "permanent (man)"))
unique(db_resurv$RS_PROJTYP)
[1] "resampling"      "permanent"       "permanent (man)"
ggplot(db_resurv, aes(RS_PROJTYP, fill=`Manipulate (y/n)`)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "Resurvey project type") + coord_flip() +
  theme(legend.position = "top")
ggsave(filename=here("output", "figures","issue11.tiff"),
       width=18,height=8,units="cm",dpi=300)

ISSUE 12: Column RS_DUPL

db_resurv %>% filter(!is.na(RS_DUPL)) %>% select(RS_CODE, RS_DUPL) %>%
  distinct()

ISSUE 13: Location uncertainty

db_resurv <- db_resurv %>%
  # Redefine precision_new, which was wrong
  mutate(precision_new = factor(ifelse(is.na(Lon_prec) & is.na(Lat_prec),
                                       0, 1)))
ggplot(db_resurv, aes(`Location uncertainty (m)`, fill = precision_new)) +
  geom_histogram( color = "black") +
  xlab("Location uncertainty (m)")

ggplot(db_resurv %>% filter(`Location uncertainty (m)` <= 500),
       aes(`Location uncertainty (m)`, fill = precision_new)) +
  geom_histogram(color = "black") +
  xlab("Location uncertainty (m) <= 500")
ggsave(filename=here("output", "figures","issue13_1.tiff"),
       width=18,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(`Location uncertainty (m)` > 500),
       aes(`Location uncertainty (m)`, fill = precision_new)) +
  geom_histogram(color = "black") +
  xlab("Location uncertainty (m) > 500")
ggsave(filename=here("output", "figures","issue13_2.tiff"),
       width=18,height=8,units="cm",dpi=300)

NO ISSUES FROM HERE

Altitude and slope values

Unique slope values:

unique((db_resurv)$`Slope (°)`) %>% str_sort()
 [1] "_"  "-"  "-1" "."  "0"  "0." "00" "03" "07" "1"  "1." "10" "11" "12" "13"
[16] "14" "15" "16" "17" "18" "19" "2"  "2." "20" "21" "22" "23" "24" "25" "26"
[31] "27" "28" "29" "3"  "30" "31" "32" "33" "34" "35" "36" "37" "38" "39" "4" 
[46] "4." "40" "41" "42" "43" "44" "45" "46" "47" "48" "5"  "5." "50" "51" "52"
[61] "55" "58" "6"  "6." "60" "65" "7"  "70" "75" "77" "78" "8"  "8." "80" "85"
[76] "9"  "9." "90" "95" NA  

Set altitude, slope and aspect as numeric:

db_resurv <- db_resurv %>%
  mutate(
    # Some altitude values have a "-" after the number,
    # convert to numeric after removing that
    Altitude = as.numeric(gsub("-", "", Altitude)),
    # Some slope values are noted as "_" or "-", these should be NA,
    # otherwise convert to numeric
    `Slope (°)` = ifelse(`Slope (°)` == "_" | `Slope (°)` == "-",
                   NA, as.numeric(`Slope (°)`)),
    # Convert aspect values to numeric
    `Aspect (°)` = as.numeric(`Aspect (°)`)
    )
Aviso: There was 1 warning in `mutate()`.
ℹ In argument: `Slope (°) = ifelse(`Slope (°)` == "_" | `Slope (°)` == "-", NA,
  as.numeric(`Slope (°)`))`.
Caused by warning in `ifelse()`:
! NAs introducidos por coerción

Histograms:

ggplot(db_resurv, aes(Altitude)) +
  geom_histogram(fill = "white", color = "black")

ggplot(db_resurv, aes(`Aspect (°)`)) +
  geom_histogram(fill = "white", color = "black")

ggplot(db_resurv, aes(`Slope (°)`)) +
  geom_histogram(fill = "white", color = "black")
ggsave(filename=here("output", "figures","issue8.tiff"),
       width=18,height=10,units="cm",dpi=300)

range(db_resurv$`Slope (°)`, na.rm=T)
[1] -1 95

Add columns date and year

db_resurv <- db_resurv %>%
  mutate(date = dmy(`Date of recording`), year = year(date))

Histograms:

ggplot(db_resurv, aes(year)) + geom_histogram(fill = "white", color = "black")

Plot size

ggplot(db_resurv, aes(`Relevé area (m²)`)) +
  geom_histogram(fill = "white", color = "black")

Observations with no info on plot size:

nrow(db_resurv %>% filter(is.na(`Relevé area (m²)`)))
[1] 19599

Cover values (total, trees, shrubs, herbs, mossess)

db_resurv %>%
  pivot_longer(cols = c(`Cover total (%)`, `Cover tree layer (%)`,
                        `Cover shrub layer (%)`, `Cover herb layer (%)`,
                        `Cover moss layer (%)`),
               names_to = "variable", values_to = "value") %>%
  ggplot(aes(x = value)) +
  geom_histogram(fill = "white", color = "black", bins = 10) +
  facet_wrap(~ variable, scales = "free") +
  labs(x = "Value", y = "Frequency")

db_resurv %>%
  reframe(across(c(`Cover total (%)`, `Cover tree layer (%)`,
                     `Cover shrub layer (%)`, `Cover herb layer (%)`,
                     `Cover moss layer (%)`), ~range(., na.rm = TRUE)))

All values OK.

Mosses and lichens identified

ggplot(db_resurv, aes(`Mosses identified (Y/N)`)) + geom_bar()

ggplot(db_resurv, aes(`Lichens identified (Y/N)`)) + geom_bar()

NA in most cases.

All resurveys for each resurvey plot to send to Bea

db_Europa<- db_resurv %>%
  group_by(RS_CODE, `ReSurvey site`,
           # If ReSurvey plot is not NA, 
           # group by RS_CODE, `ReSurvey site`, `ReSurvey plot`
           `ReSurvey plot` = ifelse(is.na(`ReSurvey plot`), 
                                    NA_character_, `ReSurvey plot`),
           # If ReSurvey plot is NA, group by coordinates
           # Create a unique grouping variable that uses coordinates
           # only when conditions are met
           group_coords = ifelse(is.na(`ReSurvey plot`),
                                 paste(Lon_updated, Lat_updated), NA_character_)
  ) %>%
  # Add unique identifiers for each plot.
  # These are based on the unique combination of RS_CODE, ReSurvey site and 
  # ReSurvey plot (When ReSurvey plot is not NA)
  # and on the unique combination of RS_CODE, ReSurvey site 
  # and updated coordinates (When ReSurvey plot is NA)
  mutate(plot_unique_id = cur_group_id()) %>%
  select(PlotObservationID, Country, `Date of recording`, RS_CODE,
         `ReSurvey site`, `ReSurvey plot`, Lon_updated, Lat_updated,
         group_coords, `Expert System`, `Location method`, plot_unique_id) %>%
  ungroup() %>%
  # Convert dates to date format and get the year
  mutate(date = dmy(`Date of recording`), year = year(date)) %>%
  select(-`Date of recording`, -date, -group_coords) %>%
  # Add unique identifiers for each observation
  mutate(obs_unique_id = row_number())
print(db_Europa, width = Inf)

Save to csv (file for us):

write_csv(db_Europa,here("data", "clean","db_Europa_20250107.csv"))

Save to csv (file for Bea, with only essential info):

write_csv(db_Europa %>% 
            select(obs_unique_id, plot_unique_id, Lon_updated, Lat_updated,
                   year),
          here("data", "clean","db_Europa_20241210_short.csv"))

Info on HabitatID from DK

Based on information got from Jesper.

Read the data sent by Jesper from DK

db_DK_J<-read_tsv(here("data", "raw",
                       "DK_Naturdata_Res_habitat_hab_codes_Jesper",
                  "DK_Naturdata_Res_habitat_hab_codes.txt"))
Rows: 158800 Columns: 9── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (2): HABITAT, Dataset
dbl (7): PlotObservationID, RELEVE_NR, CIRC_ID, PLOT5_ID, PLOT15_ID, Access ...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Add info on HabitatID to db_resurv

db_resurv <- db_resurv %>%
  # Keeping all obs in db_resurv but not all in db_DK_J
  left_join(db_DK_J %>% select(PlotObservationID, HabitatID))
Joining with `by = join_by(PlotObservationID)`

List of HabitatID

print(db_resurv %>% distinct(HabitatID), n = 100)

Write csv:

write_csv(db_resurv %>% distinct(HabitatID),
          here("data", "clean","list_HabitatID_DK_resurv.csv"))

Cases without HabitatID info

Cases without ESy EUNIS habitat:

nrow(db_resurv %>% filter(is.na(`Expert System`)))/nrow(db_resurv)
[1] 0.6814982

Cases without ESy EUNIS habitat but with HabitatID from DK:

nrow(db_resurv %>% filter(is.na(`Expert System`)&!is.na(HabitatID)))/nrow(db_resurv)
[1] 0.2727117

Cases without ESy EUNIS habitat and without HABITAT from DK:

nrow(db_resurv %>% 
       filter(is.na(`Expert System`)&is.na(HabitatID)))/nrow(db_resurv)
[1] 0.4087865

Cases without ESy EUNIS habitat and without HabitatID from DK where data is presence / absence:

nrow(db_resurv %>%
       filter(is.na(`Expert System`) &
                is.na(HabitatID) &
                `Cover abundance scale` == "Presence/Absence"))/
  nrow(db_resurv)
[1] 0.1655851

Cases without ESy EUNIS habitat and without HabitatID from DK where data is not presence / absence:

nrow(db_resurv %>%
       filter(is.na(`Expert System`) &
                is.na(HabitatID) &
                `Cover abundance scale` != "Presence/Absence"))/
  nrow(db_resurv)
[1] 0.2432014

Change some Annex I habitat codes that were wrong

db_resurv <- db_resurv %>%
  mutate(HabitatID = as.character(HabitatID)) %>%
  mutate(HabitatID = ifelse(HabitatID == "9998", "91D0",
                            ifelse(HabitatID == "9999", "91E0", HabitatID)))

Add info on correspondences HabitatID (DK, Jesper) - EUNIS

Read correspondences file:

correspondences<-read_excel(here("data", "edited",
                                 "correspondence_HabitatID_DK.xlsx"))

Add info to db_resurv:

db_resurv <- db_resurv %>%
  # Keeping all obs in db_resurv but not all in db_DK_J
  left_join(correspondences %>% select(HabitatID, EUNIS))
Joining with `by = join_by(HabitatID)`

Correct NA values in EUNIS

db_resurv <- db_resurv %>%
  mutate(EUNIS = ifelse(EUNIS == "NA", NA, EUNIS))

Add info on EUNIS (DK) to EUNISa:

db_resurv <- db_resurv %>%
  mutate(EUNISa =
           # If EUNIS (DK) is available, add as EUNISa
           ifelse(!is.na(EUNIS), EUNIS, 
                  # Otherwise keep EUNISa
                  EUNISa),
         EUNIS_assignation = ifelse(!is.na(EUNIS), "Info from DK",
                                    ifelse(is.na(EUNISa), "Not possible",
                                           "Expert system"))) %>%
  # Remove column EUNIS (DK)
  select(-EUNIS)
ggplot(db_resurv, aes(EUNIS_assignation)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS assignation")

Generate file for ISSUE 5

write_csv(db_resurv %>%
            filter(is.na(`Expert System`)&is.na(HabitatID)),
          here("output", "csv","issue5.csv"))

Update columns for EUNIS levels and descriptions

Update the columns for the different EUNISs levels:

db_resurv <- db_resurv %>%
  mutate(
    # EUNISa levels
    EUNISa_1 = substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 2, 1)),
    EUNISa_2 = ifelse(
      nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 3, 2), 
      substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 3, 2)),
      NA_character_
    ),
    EUNISa_3 = ifelse(
      nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 4, 3), 
      substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 4, 3)),
      NA_character_
      ),
    EUNISa_4 = ifelse(
      nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 5, 4), 
      substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 5, 4)),
      NA_character_
    )
  ) %>%
  # Remove HabitatID column
  select(-HabitatID)

Update columns with descriptions for the level 1 codes:

db_resurv <- db_resurv %>%
  mutate(
    EUNISa_1_descr = case_when(
      EUNISa_1 == "V" ~ "Vegetated man-made habitats",
      EUNISa_1 == "U" ~ "Inland habitats with no or little soil",
      EUNISa_1 == "T" ~ "Forests and other wooded land",
      EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
      EUNISa_1 == "R" ~ "Grasslands",
      EUNISa_1 == "Q" ~ "Wetlands",
      EUNISa_1 == "P" ~ "Inland waters",
      EUNISa_1 == "N" ~ "Coastal habitats",
      EUNISa_1 == "MA" ~ "Marine habitats",
      TRUE ~ NA_character_
    )
  )

Number of different EUNIS codes

Recalculate how many different EUNIS codes have been assigned:

db_resurv <- db_resurv %>%
  mutate(
    # Count the number of non-NA values across the EUNIS columns
    n_EUNIS = rowSums(!is.na(select(., EUNISa:EUNISd)))
  )
ggplot(db_resurv, aes(n_EUNIS)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "Number of differnt EUNIS codes assigned") + coord_flip()

ggplot(db_resurv %>% filter(n_EUNIS > 0), aes(n_EUNIS)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "Number of differnt EUNIS codes assigned") + coord_flip()

New plot for EUNISa_1 (the first assigned EUNIS in cases of multiple assignations, level 1):

ggplot(db_resurv, aes(EUNISa_1_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 1") + coord_flip()

ggplot(db_resurv %>% filter(!is.na(EUNISa_1_descr)), aes(EUNISa_1_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 1") + coord_flip()

Add info on descriptions for EUNIS levels 2-4

descriptions<-read_excel(here("data", "edited",
                                 "EUNIS-Habitats-2021-06-01_modified.xlsx"))
# Define the columns and corresponding description column names
eunis_cols <- c("EUNISa_2", "EUNISa_3", "EUNISa_4",
                "EUNISb_2", "EUNISb_3", "EUNISb_4", 
                "EUNISc_2", "EUNISc_3", "EUNISc_4",
                "EUNISd_2", "EUNISd_3", "EUNISd_4")

# Create corresponding description column names
descr_col_names <- paste0(eunis_cols, "_descr")

# Use reduce to loop through the columns and join dynamically based on level
db_resurv <- reduce(seq_along(eunis_cols), function(data, i) {
  # Extract level number from the column name (e.g., EUNISa_2 -> 2)
  level <- as.numeric(gsub("\\D", "", eunis_cols[i]))
  
  # Filter descriptions for the corresponding level
  descriptions_level <- descriptions %>%
    filter(level == level) %>%
    select(`EUNIS 2020 code`, `EUNIS-2021 habitat name`)
  
  # Perform the left_join and rename the column dynamically
  data %>%
    left_join(
      descriptions_level,
      by = setNames("EUNIS 2020 code", eunis_cols[i])
    ) %>%
    rename(!!descr_col_names[i] := `EUNIS-2021 habitat name`)
}, .init = db_resurv)

The matching did not work sometimes, correct!

# Correct EUNISa levels 2-4 descriptions
db_resurv <- db_resurv %>%
  mutate(EUNISa_2_descr = 
           ifelse(!is.na(EUNISa_2_descr), EUNISa_2_descr,
                  case_when(
                    EUNISa_2 == "Pf" ~ "Fresh-water submerged vegetation",
                    EUNISa_2 == "Pj" ~ "Stonewort vegetation",
                    EUNISa_2 == "R4" ~ "Alpine and subalpine grasslands",
                    EUNISa_2 == "Pb" ~ "Calcareous spring and spring brook",
                    EUNISa_2 == "Qb" ~ "Wetlands",
                    EUNISa_2 == "R3" ~ "Seasonally wet and wet grasslands",
                    EUNISa_2 == "Qa" ~ "Mires",
                    EUNISa_2 == "Pa" ~ "Base-poor spring and spring brook",
                    EUNISa_2 == "Ph" ~ "Oligotrophic-water vegetation",
                    EUNISa_2 == "Pg" ~ "Fresh-water nymphaeid vegetation",
                    EUNISa_2 ==
                      "Pd" ~ "Fresh-water small pleustophyte vegetation",
                    EUNISa_2 == "Pc" ~ "Brackish-water vegetation",
                    EUNISa_2 ==
                      "Pe" ~ "Fresh-water large pleustophyte vegetation",
                    EUNISa_2 == "Pi" ~ "Dystrophic-water vegetation",
                    EUNISa_2 == "S1" ~ "Tundra",
                    EUNISa_2 ==
                      "U7" ~ "Unvegetated or sparsely vegetated gravel bars",
                    EUNISa_2 == "Q6" ~ "Periodically exposed shores",
                    TRUE ~ NA_character_)
                  ),
         EUNISa_3_descr = 
           ifelse(!is.na(EUNISa_3_descr), EUNISa_3_descr,
                  case_when(
                    EUNISa_3 =="U71" ~ "Unvegetated or sparsely vegetated gravel bar in montane and alpine regions",
                    EUNISa_3 =="Q61" ~ "Periodically exposed shore with stable, eutrophic sediments with pioneer or ephemeral vegetation",
                    EUNISa_3 =="Q62" ~ "Periodically exposed shore with stable, mesotrophic sediments with pioneer or ephemeral vegetation",
                    TRUE ~ NA_character_
                    ))
         )
# Correct EUNISb levels 2-4 descriptions
db_resurv <- db_resurv %>%
  mutate(EUNISb_2_descr = 
           ifelse(!is.na(EUNISb_2_descr), EUNISb_2_descr,
                  case_when(
                    EUNISb_2 == "Pj" ~ "Stonewort vegetation",
                    EUNISb_2 == "R4" ~ "Alpine and subalpine grasslands",
                    EUNISb_2 == "Pf" ~ "Fresh-water submerged vegetation",
                    TRUE ~ NA_character_)
                  )
         )

EUNISc and EUNISd levels 2-4 are OK.

Notes EUNIS codes - to change?

https://www.sci.muni.cz/botany/chytry/Schaminee_etal2021_EEA-Report-Aquatic-Wetland-habitats.pdf

EUNISa_2 == “Q6” : “Periodically exposed shores” EUNISa_3 = “Q61” : “Periodically exposed shore with stable, eutrophic sediments with pioneer or ephemeral vegetation” EUNISa_3 == “Q62” : “Periodically exposed shore with stable, mesotrophic sediments with pioneer or ephemeral vegetation”

This classification of Q + numbers is now coexisting in the database with Qa & Qb (metadata). How to proceed?

db_resurv %>% filter(EUNISa_1 == "Q") %>% distinct(EUNISa_2)

Plots of level-2 categories within each level 1 category

ggplot(db_resurv %>% filter(EUNISa_1 == "MA"), aes(EUNISa_2_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 2") + coord_flip() +
  ggtitle(db_resurv %>% filter(EUNISa_1 == "MA") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","MA_level2.tiff"),
       width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "N"), aes(EUNISa_2_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 2") + coord_flip() +
  ggtitle(db_resurv %>% filter(EUNISa_1 == "N") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","N_level2.tiff"),
       width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "P"), aes(EUNISa_2_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 2") + coord_flip() +
  ggtitle(db_resurv %>% filter(EUNISa_1 == "P") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","P_level2.tiff"),
       width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "Q"), aes(EUNISa_2_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 2") + coord_flip() +
  ggtitle(db_resurv %>% filter(EUNISa_1 == "Q") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","Q_level2.tiff"),
       width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "R"), aes(EUNISa_2_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 2") + coord_flip() +
  ggtitle(db_resurv %>% filter(EUNISa_1 == "R") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","R_level2.tiff"),
       width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "S"), aes(EUNISa_2_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 2") + coord_flip() +
  ggtitle(db_resurv %>% filter(EUNISa_1 == "S") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","S_level2.tiff"),
       width=16,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "T"), aes(EUNISa_2_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 2") + coord_flip() +
  ggtitle(db_resurv %>% filter(EUNISa_1 == "T") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","T_level2.tiff"),
       width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "U"), aes(EUNISa_2_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 2") + coord_flip() +
  ggtitle(db_resurv %>% filter(EUNISa_1 == "U") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","U_level2.tiff"),
       width=16,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "V"), aes(EUNISa_2_descr)) +
         geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
  labs(y = "Percentage of ReSurvey observations",
       x = "EUNIS level 2") + coord_flip() +
  ggtitle(db_resurv %>% filter(EUNISa_1 == "V") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","V_level2.tiff"),
       width=14,height=8,units="cm",dpi=300)

Save to clean data

Save so-far clean datafile for resurvey database:

write_tsv(db_resurv,here("data", "clean","db_resurv_clean.csv"))

Session info

sessionInfo()
R version 4.4.2 (2024-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 10 x64 (build 19045)

Matrix products: default


locale:
[1] LC_COLLATE=Spanish_Spain.utf8  LC_CTYPE=Spanish_Spain.utf8   
[3] LC_MONETARY=Spanish_Spain.utf8 LC_NUMERIC=C                  
[5] LC_TIME=Spanish_Spain.utf8    

time zone: Europe/Paris
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] readxl_1.4.3    purrr_1.0.2     stringr_1.5.1   ggplot2_3.5.1  
[5] lubridate_1.9.4 dplyr_1.1.4     tidyr_1.3.1     readr_2.1.5    
[9] here_1.0.1     

loaded via a namespace (and not attached):
 [1] sass_0.4.9        utf8_1.2.4        generics_0.1.3    stringi_1.8.4    
 [5] hms_1.1.3         digest_0.6.37     magrittr_2.0.3    evaluate_1.0.1   
 [9] grid_4.4.2        timechange_0.3.0  fastmap_1.2.0     cellranger_1.1.0 
[13] rprojroot_2.0.4   jsonlite_1.8.9    fansi_1.0.6       scales_1.3.0     
[17] jquerylib_0.1.4   cli_3.6.3         rlang_1.1.4       crayon_1.5.3     
[21] bit64_4.5.2       munsell_0.5.1     withr_3.0.2       cachem_1.1.0     
[25] yaml_2.3.10       tools_4.4.2       parallel_4.4.2    tzdb_0.4.0       
[29] colorspace_2.1-1  vctrs_0.6.5       R6_2.5.1          lifecycle_1.0.4  
[33] bit_4.5.0.1       vroom_1.6.5       pkgconfig_2.0.3   pillar_1.9.0     
[37] bslib_0.8.0       gtable_0.3.6      glue_1.8.0        xfun_0.49        
[41] tibble_3.2.1      tidyselect_1.2.1  knitr_1.49        farver_2.1.2     
[45] htmltools_0.5.8.1 rmarkdown_2.29    labeling_0.4.3    compiler_4.4.2   
LS0tDQp0aXRsZTogIlNjcmlwdCB0byBtYWtlIGEgZmlyc3QgY2hlY2sgb2YgdGhlIEVWQSBSZVN1cnZleSBkYXRhYmFzZSBmb3IgTU9USVZBVEUiDQpvdXRwdXQ6DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQotLS0NCg0KIyBMb2FkIGxpYnJhcmllcw0KDQpgYGB7cn0NCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkocmVhZHhsKQ0KYGBgDQoNCiMgUmVhZCB0aGUgZGF0YQ0KDQpgYGB7cn0NCmRiX3Jlc3VydjwtcmVhZF90c3YoaGVyZSgiZGF0YSIsICJlZGl0ZWQiLCAiZGJfcmVzdXJ2LmNzdiIpKQ0KYGBgDQoNCiMgUHJvYmxlbXMgKGRvIG5vdCBhZmZlY3QgdXMpDQoNCmBgYHtyfQ0KcHJvYmxlbXM8LXByb2JsZW1zKGRiX3Jlc3VydikNCnNvcnQodW5pcXVlKHByb2JsZW1zJGNvbCkpDQpuYW1lcyhkYl9yZXN1cnZbYyg3LDEzKV0pDQpgYGANCg0KTm8gcHJvYmxlbXMgcmVhbGx5IQ0KDQojIFVwZGF0ZSBjb29yZGluYXRlcw0KDQpDcmVhdGUgbmV3IGNvbHVtbiB3aXRoIG9sZCBjb29yZGluYXRlcyBpZiBuZXcgbm90IGF2YWlsYWJsZSwgYW5kIHdpdGggbmV3IGlmIGF2YWlsYWJsZS4NCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoTG9uX3VwZGF0ZWQgPSBpZmVsc2UoaXMubmEoTG9uX3ByZWMpLExvbmdpdHVkZSxMb25fcHJlYyksDQogICAgICAgICBMYXRfdXBkYXRlZCA9IGlmZWxzZShpcy5uYShMYXRfcHJlYyksTGF0aXR1ZGUsTGF0X3ByZWMpKQ0KYGBgDQoNCmBgYHtyfQ0KcHJpbnQoZGJfcmVzdXJ2LCB3aWR0aCA9IEluZikNCmBgYA0KDQojIElTU1VFIDE6IFJlU3VydmV5IHBsb3QgaXMgTkENCg0KQ2FyZWZ1bCEgU29tZXRpbWVzIFJlU3VydmV5IHBsb3QgaXMgTkEuIA0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2JT4lZmlsdGVyKGlzLm5hKFJTX0NPREUpKSkNCm5yb3coZGJfcmVzdXJ2JT4lZmlsdGVyKGlzLm5hKGBSZVN1cnZleSBzaXRlYCkpKQ0KbnJvdyhkYl9yZXN1cnYlPiVmaWx0ZXIoaXMubmEoYFJlU3VydmV5IHBsb3RgKSkpDQpgYGANCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoaXMubmEoYFJlU3VydmV5IHBsb3RgKSksDQogICAgICAgICAgaGVyZSgib3V0cHV0IiwgImNzdiIsImlzc3VlMS5jc3YiKSkNCmBgYA0KDQojIElTU1VFIDI6IENvb3JkaW5hdGVzIGFyZSBOQQ0KDQpDb29yZGluYXRlcyBhcmUgYWxzbyBOQSBpbiBzb21lIGNhc2VzLg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2JT4lZmlsdGVyKGlzLm5hKExvbl91cGRhdGVkKSAmIGlzLm5hKExhdF91cGRhdGVkICkpKQ0KYGBgDQoNCmBgYHtyfQ0Kd3JpdGVfY3N2KGRiX3Jlc3VydiAlPiUgZmlsdGVyKGlzLm5hKExvbl91cGRhdGVkKSAmIGlzLm5hKExhdF91cGRhdGVkKSksDQogICAgICAgICAgaGVyZSgib3V0cHV0IiwgImNzdiIsImlzc3VlMi5jc3YiKSkNCmBgYA0KDQpCdXQgUmVzdXJ2ZXkgcGxvdCBhbmQgY29vcmRpbmF0ZXMgYXJlIG5ldmVyIG1pc3NpbmcgYXQgdGhlIHNhbWUgdGltZS4NCg0KYGBge3J9DQpucm93KGRiX3Jlc3VydiU+JQ0KICAgICAgIGZpbHRlcihpcy5uYShgUmVTdXJ2ZXkgcGxvdGApICYgaXMubmEoTG9uX3VwZGF0ZWQpICYgaXMubmEoTGF0X3VwZGF0ZWQpKSkNCmBgYA0KDQojIElTU1VFIDM6IERpZmZlcmVudCBSZVN1cnZleSBvYnNlcnZhdGlvbnMgd2l0aGluIHRoZSBzYW1lIHBsb3QgaGF2ZSBkaWZmZXJlbnQgY29vcmRpbmF0ZXMNCg0KQ2FyZWZ1bCEgSW4gc29tZSBjYXNlcyBkaWZmZXJlbnQgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIHdpdGhpbiB0aGUgc2FtZSBwbG90IGhhdmUgZGlmZmVyZW50IGNvb3JkaW5hdGVzLiANCg0KQ3JlYXRlIHR3byBuZXcgY29sdW1ucyBpbiBkYl9yZXN1cnY6IGNvb3JkaW5hdGVzX2VxdWFsIGluZGljYXRpbmcgaWYgY29vcmRpbmF0ZXMgYXJlIGV4YWN0bHkgZXF1YWwgYmV0d2VlbiBSZVN1cnZleSBvYnNlcnZhdGlvbnMsIGFuZCBjb29yZGluYXRlc19jb25zaXN0ZW50LCBpbmRpY2F0aW5nIGlmIGNvb3JkaW5hdGVzIGFyZSBjb25zaXN0ZW50IGJldHdlZW4gUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIChjb25zaXN0ZW50IG1lYW5pbmcgdGhhdCBkaWZmZXJlbmNlIDwgMC4wMDEgZGVncmVlcykuDQoNCmBgYHtyfQ0KIyBEZWZpbmUgYSB0aHJlc2hvbGQgKGUuZy4sIDAuMDAxIGRlZ3JlZXMgZm9yIGxvbmdpdHVkZS9sYXRpdHVkZSBkaWZmZXJlbmNlcykNCnRocmVzaG9sZCA8LSAwLjAwMQ0KDQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBncm91cF9ieShSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCkgJT4lDQogIG11dGF0ZSgNCiAgICBsb25fcmFuZ2UgPSBpZmVsc2UoYWxsKGlzLm5hKExvbl91cGRhdGVkKSksIE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4KExvbl91cGRhdGVkLCBuYS5ybSA9IFQpIC0gDQogICAgICAgICAgICAgICAgICAgICAgICAgbWluKExvbl91cGRhdGVkLCBuYS5ybSA9IFQpKSwNCiAgICBsYXRfcmFuZ2UgPSBpZmVsc2UoYWxsKGlzLm5hKExhdF91cGRhdGVkKSksIE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4KExhdF91cGRhdGVkLCBuYS5ybSA9IFQpIC0gDQogICAgICAgICAgICAgICAgICAgICAgICAgbWluKExhdF91cGRhdGVkLCBuYS5ybSA9IFQpKSwNCiAgICBjb29yZGluYXRlc19lcXVhbCA9IGlmZWxzZShpcy5uYShMb25fdXBkYXRlZCkgJiBpcy5uYShMYXRfdXBkYXRlZCksIE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvbl9yYW5nZSA9PSAwICYgbGF0X3JhbmdlID09IDApLA0KICAgIGNvb3JkaW5hdGVzX2NvbnNpc3RlbnQgPSBpZmVsc2UoaXMubmEoTG9uX3VwZGF0ZWQpICYgaXMubmEoTGF0X3VwZGF0ZWQpLCBOQSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvbl9yYW5nZSA8IHRocmVzaG9sZCAmIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXRfcmFuZ2UgPCB0aHJlc2hvbGQpDQogICkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KC1sb25fcmFuZ2UsIC1sYXRfcmFuZ2UpDQpgYGANCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoY29vcmRpbmF0ZXNfZXF1YWw9PUZBTFNFKSwNCiAgICAgICAgICBoZXJlKCJvdXRwdXQiLCAiY3N2IiwiaXNzdWUzLmNzdiIpKQ0KYGBgDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2ICU+JSANCiAgZ3JvdXBfYnkoUlNfQ09ERSxgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCkgJT4lDQogIHN1bW1hcml6ZShpc19lcXVhbCA9IGFsbChjb29yZGluYXRlc19lcXVhbCksDQogICAgICAgICAgICBpc19jb25zaXN0ZW50ID0gYWxsKGNvb3JkaW5hdGVzX2NvbnNpc3RlbnQpLA0KICAgICAgICAgICAgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIG11dGF0ZShjb29yZGluYXRlX3N0YXR1cyA9IGNhc2Vfd2hlbigNCiAgICBpc19lcXVhbCB+ICJFcXVhbCIsDQogICAgIWlzX2VxdWFsICYgaXNfY29uc2lzdGVudCB+ICJDb25zaXN0ZW50ICg8IDAuMDAxwropIiwNCiAgICAhaXNfZXF1YWwgJiAhaXNfY29uc2lzdGVudCB+ICJJbmNvbnNpc3RlbnQgKD4gMC4wMDHCuikiKSkgJT4lDQogIGNvdW50KGNvb3JkaW5hdGVfc3RhdHVzKSU+JQ0KICBtdXRhdGUocGVyY2VudGFnZSA9IG4gLyBzdW0obikgKiAxMDApICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBwZXJjZW50YWdlLCB5ID0gY29vcmRpbmF0ZV9zdGF0dXMsIGZpbGwgPSBjb29yZGluYXRlX3N0YXR1cykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZTAocm91bmQocGVyY2VudGFnZSwgMSksICIlIikpLA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksIHNpemUgPSAzKSArIA0KICBsYWJzKHggPSAiUGVyY2VudGFnZSBvZiBQbG90cyIsIHkgPSBOVUxMKSArDQogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWUzLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xMCxoZWlnaHQ9Nyx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KIyBJU1NVRSA0OiBTb21lIHBsb3RzIGhhdmUgb25seSBvbmUgcmVzdXJ2ZXkNCg0KV2hlbiBSZVN1cnZleSBwbG90IGlzIG5vdCBOQSwgdXNlIHRoZSB1bmlxdWUgY29tYmluYXRpb24gb2YgUlNfQ09ERSwgUmVTdXJ2ZXkgc2l0ZSBhbmQgUmVTdXJ2ZXkgcGxvdCB0byB1bmlxdWVseSBkZWZpbmUgZWFjaCBSZVN1cnZleSBwbG90IChhcyBkZWZpbmVkIGluIG1ldGFkYXRhKS4gV2hlbiBSZVN1cnZleSBwbG90IGlzIE5BICgxMTEgcm93cyksIHVzZSB0aGUgdW5pcXVlIGNvbWJpbmF0aW9uIG9mIFJTX0NPREUsIFJlU3VydmV5IHNpdGUgYW5kIHVwZGF0ZWQgY29vcmRpbmF0ZXMgdG8gdW5pcXVlbHkgZGVmaW5lIGVhY2ggUmVTdXJ2ZXkgcGxvdC4gQ2hlY2sgaG93IG1hbnkgcmVzdXJ2ZXlzIChpLmUuIGRpZmZlcmVudCB5ZWFycyBhcmUgdGhlcmUgZm9yIGVhY2ggdW5pcXVlIGNvbWJpbmF0aW9uKS4NCg0KYGBge3J9DQpjb3VudF9yZXN1cnZleXMgPC0gZGJfcmVzdXJ2ICU+JQ0KICAjIENvbnZlcnQgZGF0ZXMgdG8gZGF0ZSBmb3JtYXQgYW5kIGdldCB0aGUgeWVhcg0KICBtdXRhdGUoZGF0ZSA9IGRteShgRGF0ZSBvZiByZWNvcmRpbmdgKSwgeWVhciA9IHllYXIoZGF0ZSkpICU+JQ0KICBncm91cF9ieShSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsDQogICAgICAgICAgICMgSWYgUmVTdXJ2ZXkgcGxvdCBpcyBub3QgTkEsIA0KICAgICAgICAgICAjIGdyb3VwIGJ5IFJTX0NPREUsIGBSZVN1cnZleSBzaXRlYCwgYFJlU3VydmV5IHBsb3RgDQogICAgICAgICAgIGBSZVN1cnZleSBwbG90YCA9IGlmZWxzZShpcy5uYShgUmVTdXJ2ZXkgcGxvdGApLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BX2NoYXJhY3Rlcl8sIGBSZVN1cnZleSBwbG90YCksDQogICAgICAgICAgICMgSWYgUmVTdXJ2ZXkgcGxvdCBpcyBOQSwgZ3JvdXAgYnkgY29vcmRpbmF0ZXMNCiAgICAgICAgICAgTG9uX3VwZGF0ZWQgPSBpZmVsc2UoaXMubmEoYFJlU3VydmV5IHBsb3RgKSwgTG9uX3VwZGF0ZWQsIE5BX3JlYWxfKSwNCiAgICAgICAgICAgTGF0X3VwZGF0ZWQgPSBpZmVsc2UoaXMubmEoYFJlU3VydmV5IHBsb3RgKSAsIExhdF91cGRhdGVkLCBOQV9yZWFsXykNCiAgKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgICMgR2V0IGhvdyBtYW55IGRpZmZlcmVudCB5ZWFycyBmb3IgZWFjaCB1bmlxdWUgZ3JvdXANCiAgICBkaXN0aW5jdF95ZWFycz1uX2Rpc3RpbmN0KHllYXIpLCANCiAgICAjIEdldCBob3cgbWFueSBkaWZmZXJlbnQgZGF0ZXMgZm9yIGVhY2ggdW5pcXVlIGdyb3VwDQogICAgZGlzdGluY3RfZGF0ZXM9bl9kaXN0aW5jdChkYXRlKSwgLmdyb3VwcyA9ICJkcm9wIikNCmBgYA0KDQpTdW1tYXJ5IHN0YXRzOg0KDQpgYGB7cn0NCnN1bW1hcnkoY291bnRfcmVzdXJ2ZXlzJGRpc3RpbmN0X3llYXJzKQ0Kc2QoY291bnRfcmVzdXJ2ZXlzJGRpc3RpbmN0X3llYXJzKQ0KYGBgDQoNCkhpc3RvZ3JhbXM6DQoNCmBgYHtyfQ0KIyBGb3IgYWxsIGRhdGENCmdncGxvdChjb3VudF9yZXN1cnZleXMsIGFlcyh4ID0gZGlzdGluY3RfeWVhcnMpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siLCBiaW5zID0gNTUpKw0KICB4bGFiKCJOdW1iZXIgb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIChkaWZmZXJlbnQgeWVhcnMpIikgKw0KICB5bGFiKCJOdW1iZXIgb2YgcGxvdHMiKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWU0LnRpZmYiKSwNCiAgICAgICB3aWR0aD0xMSxoZWlnaHQ9Nyx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KTnVtYmVyIGFuZCBwcm9wb3J0aW9uIG9mIHBsb3RzIHdpdGggb25seSAxIHJlc3VydmV5IChzaG91bGQgbm90IGJlIHNvISkNCg0KYGBge3J9DQpucm93KGNvdW50X3Jlc3VydmV5cyU+JWZpbHRlcihkaXN0aW5jdF95ZWFycz09MSkpDQpucm93KGNvdW50X3Jlc3VydmV5cyU+JWZpbHRlcihkaXN0aW5jdF95ZWFycz09MSkpL25yb3coY291bnRfcmVzdXJ2ZXlzKQ0KYGBgDQoNCmBgYHtyfQ0Kd3JpdGVfY3N2KGNvdW50X3Jlc3VydmV5cyU+JWZpbHRlcihkaXN0aW5jdF95ZWFycz09MSksDQogICAgICAgICAgaGVyZSgib3V0cHV0IiwgImNzdiIsImlzc3VlNC5jc3YiKSkNCmBgYA0KDQojIElTU1VFIDU6IERhdGFzZXRzIHdpdGggb25seSBwcmVzZW5jZS9hYnNlbmNlDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2ICU+JQ0KICBmaWx0ZXIoYENvdmVyIGFidW5kYW5jZSBzY2FsZWA9PSJQcmVzZW5jZS9BYnNlbmNlIikgJT4lDQogIGRpc3RpbmN0KERhdGFzZXQpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSANCiAgICAgICAgIG11dGF0ZShwcmVzX29yX2FiID1pZmVsc2UoYENvdmVyIGFidW5kYW5jZSBzY2FsZWA9PSJQcmVzZW5jZS9BYnNlbmNlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlByZXNlbmNlL0Fic2VuY2UiLCAiQWJ1bmRhbmNlIiksDQogICAgICAgICAgICAgICAgREtfTmF0dXJkYXRhX1JlcyA9IGlmZWxzZShEYXRhc2V0ID09ICJES19OYXR1cmRhdGFfUmVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJZIiwgIk4iKSksDQogICAgICAgICAgICAgICAgYWVzKHByZXNfb3JfYWIsIGZpbGwgPSBES19OYXR1cmRhdGFfUmVzKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSIsIHggPSBOVUxMKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWU1LnRpZmYiKSwNCiAgICAgICB3aWR0aD0xMixoZWlnaHQ9Nyx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KDQpGb3IgREtfTmF0dXJkYXRhX1JlcyAtIGluZm8gYWJvdXQgaGFiaXRhdCBmcm9tIEplcmtlcidzIGZpbGUgKHNlZSBiZWxvdykuDQoNCiMgSVNTVUUgNjogT2JzZXJ2YXRpb25zIHdpdGggd3JvbmcgY291bnRyeSAoR0lTKQ0KDQpSZWFkIHRleHQgZmlsZSB3cm9uZ19jb3VudHJpZXMgb2J0YWluZWQgaW4gQXJjR0lTOg0KDQpgYGB7cn0NCndyb25nX2NvdW50cmllcyA8LSByZWFkX2RlbGltKGhlcmUoImRhdGEiLCAiY2xlYW4iLCJ3cm9uZ19jb3VudHJpZXMudHh0IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxpbSA9ICI7IikNCmBgYA0KDQpgYGB7cn0NCndyaXRlX2Nzdih3cm9uZ19jb3VudHJpZXMsIGhlcmUoIm91dHB1dCIsICJjc3YiLCJpc3N1ZTYuY3N2IikpDQpgYGANCg0KIyBJU1NVRSA3OiBEaWZmZXJlbnQgY292ZXIgYWJ1bmRhbmNlIHNjYWxlcw0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhgQ292ZXIgYWJ1bmRhbmNlIHNjYWxlYCkpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwgeCA9ICJDb3ZlciBhYnVuZGFuY2Ugc2NhbGUiKSArDQogIGNvb3JkX2ZsaXAoKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWU3LnRpZmYiKSwNCiAgICAgICB3aWR0aD0xOCxoZWlnaHQ9MTAsdW5pdHM9ImNtIixkcGk9MzAwKQ0KYGBgDQoNCiMgSVNTVUUgODogV3JvbmcgRVVOSVMgY29kZXMNCg0KVXNlZCB0aGlzIGluZm8gaW4gbWV0YWRhdGEgZmlsZToNCg0KRXhwZXJ0IHN5c3RlbSBjbGFzc2lmaWNhdGlvbiB0byBFVU5JUyBoYWJpdGF0cyAoaHR0cHM6Ly96ZW5vZG8ub3JnL3JlY29yZHMvNDgxMjczNiA7IGh0dHBzOi8vZmxvcmF2ZWcuZXUvaGFiaXRhdC8pLiANCkkgYW0gc2VuZGluZyB5b3UgbGVnZW5kIGZvciBFVU5JUyBjbGFzc2lmaWNhdGlvbiB2ZXJzaW9uIDIwMjItMTAtMTYgd2l0aCBhbGwgY29kZXMgYW5kIG1lYW5pbmdzLCBkaXJlY3RseSBwcmVwYXJlZCBmcm9tIGV4cGVydCBzeXN0ZW0gZmlsZSAoc2Vjb25kIHNoZWV0KSAtIGl0IGlzIHNsaWdodGx5IGRpZmZlcmVudCBmcm9tIHB1Ymxpc2hlZCB2ZXJzaW9uIGluIFpFTk9ETyAoaHR0cHM6Ly96ZW5vZG8ub3JnL3JlY29yZHMvNDgxMjczNiAsIGxpdHRsZSBiaXQgb2xkIGRhdGVkIG5vdykgYW5kIGZyb20gaHR0cHM6Ly9mbG9yYXZlZy5ldS9oYWJpdGF0LyAobGl0dGxlIGJpdCBuZXdlciB0aGFuIGluIGN1cnJlbnQgRVZBIHZlcnNpb24pLg0KDQpRYSA9IG1pcmVzIGFuZCBRYiA9IHdldGxhbmRzDQpQIHVuaXRzIOKAkyBpbiBmbG9yYXZlZy5ldSB0aGVyZSBpcyBzbGlnaHRseSBkaWZmZXJlbnQgY2xhc3NpZmljYXRpb24gKGh0dHBzOi8vZmxvcmF2ZWcuZXUvaGFiaXRhdC9vdmVydmlldy9QKSwgYnV0IGluIEVWQSBpcyBzdGlsbCB0aGlzIGNsYXNzaWZpY2F0aW9uIG9mIFA6DQoNClAgU3VyZmFjZSB3YXRlcnMNClBhIEJhc2UtcG9vciBzcHJpbmcgYW5kIHNwcmluZyBicm9vaw0KUGIgQ2FsY2FyZW91cyBzcHJpbmcgYW5kIHNwcmluZyBicm9vaw0KUGMgQnJhY2tpc2gtd2F0ZXIgdmVnZXRhdGlvbg0KUGQgRnJlc2gtd2F0ZXIgc21hbGwgcGxldXN0b3BoeXRlIHZlZ2V0YXRpb24NClBlIEZyZXNoLXdhdGVyIGxhcmdlIHBsZXVzdG9waHl0ZSB2ZWdldGF0aW9uDQpQZiBGcmVzaC13YXRlciBzdWJtZXJnZWQgdmVnZXRhdGlvbg0KUGcgRnJlc2gtd2F0ZXIgbnltcGhhZWlkIHZlZ2V0YXRpb24NClBoIE9saWdvdHJvcGhpYy13YXRlciB2ZWdldGF0aW9uDQpQaSBEeXN0cm9waGljLXdhdGVyIHZlZ2V0YXRpb24NClBqIFN0b25ld29ydCB2ZWdldGF0aW9uIA0KDQpQcmVzZW5jZSBvZiDigJwh4oCdIHNpbXBseSBtZWFucyB0aGF0IGZvciBvbmUgdW5pdCB0aGVyZSBhcmUgdHdvIG9yIG1vcmUgZGlmZmVyZW50IGZvcm11bGFzLCBlLmcuIFIxMSBhbmQgUjExIS4gU28gaXQgaXMgb25seSB0ZWNobmljYWwgc3R1ZmYuDQoNCk11bHRpcGxlIGFzc2lnbm1lbnQgb2YgcmVsZXbDqSDigJMgbm8gcHJpb3JpdHksIGFscGhhYmV0aWNhbCBvcmRlciwgZS5nLiBOMTYhLFM2NixTODEgbWVhbnMgdGhhdCByZWxldsOpIGNhbiBiZSBhc3NpZ25lZCB0byBhbGwgMyB1bml0czogTjE2IE1lZGl0ZXJyYW5lYW4gYW5kIE1hY2Fyb25lc2lhbiBjb2FzdGFsIGR1bmUgZ3Jhc3NsYW5kIChncmV5IGR1bmUpLCBTNjYgTWVkaXRlcnJhbmVhbiBoYWxvLW5pdHJvcGhpbG91cyBzY3J1YiBhbmQgUzgxIENhbmFyaWFuIHhlcm9waHl0aWMgc2NydWINCg0KTm8gdmFsdWUgcHJlc2VudCBpbiBFeHBlcnQgU3lzdGVtIOKAkyByZWxldsOpIGRpZG7CtHQgZW50ZXIgZXhwZXJ0IHN5c3RlbSBjbGFzc2lmaWNhdGlvbiAoPSBpdCBtZWFucyB0aGF0IHNvbWUgcHJlcmVxdWlzaXRlcyBhcmUgbWlzc2luZykNCg0K4oCcfuKAnSDigJMgcmVsZXbDqSBlbnRlcmVkIGV4cGVydCBjbGFzc2lmaWNhdGlvbiBob3dldmVyIHdhcyBub3QgY2xhc3NpZmllZCB0byBhbnkgRVVOSVMgdW5pdA0KKw0KDQpDbGVhbiBpbmZvIG9uIEV4cGVydCBzeXN0ZW0gY29sdW1uIGFuZCBzZXBhcmF0ZSBpdCB3aGVuIHRoZXJlIGFyZSBzZXZlcmFsIGNvZGVzLg0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogIG11dGF0ZSgNCiAgICAjIENsZWFuICdFeHBlcnQgU3lzdGVtJyBjb2x1bW4gYnkgcmVtb3ZpbmcgIiEiIGFuZCByZXBsYWNpbmcgIn4iIHdpdGggTkENCiAgICBgRXhwZXJ0IFN5c3RlbWAgPSBjYXNlX3doZW4oDQogICAgICBgRXhwZXJ0IFN5c3RlbWAgPT0gIn4iIH4gTkFfY2hhcmFjdGVyXywgICMgUmVwbGFjZSAifiIgd2l0aCBOQQ0KICAgICAgVFJVRSB+IHN0cl9yZXBsYWNlX2FsbChgRXhwZXJ0IFN5c3RlbWAsICIhIiwgIiIpICAjIFJlbW92ZSAiISINCiAgICApDQogICkgJT4lDQogICMgU2VwYXJhdGUgdGhlIHZhbHVlcyBpbiAnRXhwZXJ0IFN5c3RlbScgaW50byBtdWx0aXBsZSBjb2x1bW5zDQogIHNlcGFyYXRlKA0KICAgIGBFeHBlcnQgU3lzdGVtYCwNCiAgICBpbnRvID0gYygiRVVOSVNhIiwgIkVVTklTYiIsICJFVU5JU2MiLCAiRVVOSVNkIiksDQogICAgc2VwID0gIiwiLA0KICAgIGV4dHJhID0gImRyb3AiLCAgIyBEcm9wIGV4dHJhIHZhbHVlcyBpZiB0aGVyZSBhcmUgbW9yZSB0aGFuIGNvbHVtbnMNCiAgICBmaWxsID0gInJpZ2h0IiwgICAjIEZpbGwgbWlzc2luZyB2YWx1ZXMgd2l0aCBOQSBmb3IgY2FzZXMgd2l0aCBmZXdlciB2YWx1ZXMNCiAgICByZW1vdmUgPSBGQUxTRSAgICAjIEtlZXAgdGhlIG9yaWdpbmFsICdFeHBlcnQgU3lzdGVtJyBjb2x1bW4NCiAgKQ0KYGBgDQoNCkNhbGN1bGF0ZSBob3cgbWFueSBkaWZmZXJlbnQgRVVOSVMgY29kZXMgaGF2ZSBiZWVuIGFzc2lnbmVkOg0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogIG11dGF0ZSgNCiAgICAjIENvdW50IHRoZSBudW1iZXIgb2Ygbm9uLU5BIHZhbHVlcyBhY3Jvc3MgdGhlIEVVTklTIGNvbHVtbnMNCiAgICBuX0VVTklTID0gcm93U3VtcyghaXMubmEoc2VsZWN0KC4sIHN0YXJ0c193aXRoKCJFVU5JUyIpKSkpDQogICkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhuX0VVTklTKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiTnVtYmVyIG9mIGRpZmZlcm50IEVVTklTIGNvZGVzIGFzc2lnbmVkIikgKyBjb29yZF9mbGlwKCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihuX0VVTklTID4gMCksIGFlcyhuX0VVTklTKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiTnVtYmVyIG9mIGRpZmZlcm50IEVVTklTIGNvZGVzIGFzc2lnbmVkIikgKyBjb29yZF9mbGlwKCkNCmBgYA0KDQpDb3JyZWN0IHNvbWUgRVVOSVMgY29kZXMgdGhhdCBhcmUgcHJvYmFibHkgd3Jvbmc6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2IDwtIGRiX3Jlc3VydiAlPiUNCiAgbXV0YXRlKGFjcm9zcyhzdGFydHNfd2l0aCgiRVVOSVMiKSwgfiBjYXNlX3doZW4oDQogICAgLiA9PSAiTjE2TSIgfiAiTjE2IiwNCiAgICAuID09ICJTYSIgfiAiVjQiLA0KICAgIC4gPT0gIlNiIiB+ICJWNSIsDQogICAgLiA9PSAiVDFDVCIgfiAiVDFDIiwNCiAgICAuID09ICJOMTVBIiB+ICJOMTUiLA0KICAgIFRSVUUgfiAuDQogICkpKQ0KYGBgDQoNCkFkZCBjb2x1bW5zIGZvciB0aGUgZGlmZmVyZW50IEVVTklTIGxldmVsczoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoDQogICAgIyBFVU5JU2EgbGV2ZWxzDQogICAgRVVOSVNhXzEgPSBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCAyLCAxKSksDQogICAgRVVOSVNhXzIgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2EpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYSwgIk1BIiksIDMsIDIpLCANCiAgICAgIHN1YnN0cihFVU5JU2EsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYSwgIk1BIiksIDMsIDIpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTYV8zID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNhKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA0LCAzKSwgDQogICAgICBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA0LCAzKSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgICApLA0KICAgIEVVTklTYV80ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNhKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKSwNCiAgICANCiAgICAjIEVVTklTYiBsZXZlbHMNCiAgICBFVU5JU2JfMSA9IHN1YnN0cihFVU5JU2IsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYiwgIk1BIiksIDIsIDEpKSwNCiAgICBFVU5JU2JfMiA9IGlmZWxzZSgNCiAgICAgIG5jaGFyKEVVTklTYikgPj0gaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNiLCAiTUEiKSwgMywgMiksIA0KICAgICAgc3Vic3RyKEVVTklTYiwgMSwgaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNiLCAiTUEiKSwgMywgMikpLA0KICAgICAgTkFfY2hhcmFjdGVyXw0KICAgICksDQogICAgRVVOSVNiXzMgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2IpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYiwgIk1BIiksIDQsIDMpLCANCiAgICAgIHN1YnN0cihFVU5JU2IsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYiwgIk1BIiksIDQsIDMpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTYl80ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNiKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2IsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNiLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2IsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKSwNCiAgICANCiAgICAjIEVVTklTYyBsZXZlbHMNCiAgICBFVU5JU2NfMSA9IHN1YnN0cihFVU5JU2MsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYywgIk1BIiksIDIsIDEpKSwNCiAgICBFVU5JU2NfMiA9IGlmZWxzZSgNCiAgICAgIG5jaGFyKEVVTklTYykgPj0gaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNjLCAiTUEiKSwgMywgMiksIA0KICAgICAgc3Vic3RyKEVVTklTYywgMSwgaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNjLCAiTUEiKSwgMywgMikpLA0KICAgICAgTkFfY2hhcmFjdGVyXw0KICAgICksDQogICAgRVVOSVNjXzMgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2MpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYywgIk1BIiksIDQsIDMpLCANCiAgICAgIHN1YnN0cihFVU5JU2MsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYywgIk1BIiksIDQsIDMpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTY180ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNjKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2MsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNjLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2MsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKSwNCiAgICANCiAgICAjIEVVTklTZCBsZXZlbHMNCiAgICBFVU5JU2RfMSA9IHN1YnN0cihFVU5JU2QsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYywgIk1BIiksIDIsIDEpKSwNCiAgICBFVU5JU2RfMiA9IGlmZWxzZSgNCiAgICAgIG5jaGFyKEVVTklTZCkgPj0gaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNkLCAiTUEiKSwgMywgMiksIA0KICAgICAgc3Vic3RyKEVVTklTZCwgMSwgaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNkLCAiTUEiKSwgMywgMikpLA0KICAgICAgTkFfY2hhcmFjdGVyXw0KICAgICksDQogICAgRVVOSVNkXzMgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2QpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTZCwgIk1BIiksIDQsIDMpLCANCiAgICAgIHN1YnN0cihFVU5JU2QsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTZCwgIk1BIiksIDQsIDMpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTZF80ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNkKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2QsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNkLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2QsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApDQpgYGANCg0KQ3JlYXRlIG5ldyBjb2x1bW5zIHdpdGggZGVzY3JpcHRpb25zIGZvciB0aGUgbGV2ZWwgMSBjb2RlczoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoDQogICAgRVVOSVNhXzFfZGVzY3IgPSBjYXNlX3doZW4oDQogICAgICBFVU5JU2FfMSA9PSAiViIgfiAiVmVnZXRhdGVkIG1hbi1tYWRlIGhhYml0YXRzIiwNCiAgICAgIEVVTklTYV8xID09ICJVIiB+ICJJbmxhbmQgaGFiaXRhdHMgd2l0aCBubyBvciBsaXR0bGUgc29pbCIsDQogICAgICBFVU5JU2FfMSA9PSAiVCIgfiAiRm9yZXN0cyBhbmQgb3RoZXIgd29vZGVkIGxhbmQiLA0KICAgICAgRVVOSVNhXzEgPT0gIlMiIH4gIkhlYXRobGFuZHMsIHNjcnViIGFuZCB0dW5kcmEiLA0KICAgICAgRVVOSVNhXzEgPT0gIlIiIH4gIkdyYXNzbGFuZHMiLA0KICAgICAgRVVOSVNhXzEgPT0gIlEiIH4gIldldGxhbmRzIiwNCiAgICAgIEVVTklTYV8xID09ICJQIiB+ICJJbmxhbmQgd2F0ZXJzIiwNCiAgICAgIEVVTklTYV8xID09ICJOIiB+ICJDb2FzdGFsIGhhYml0YXRzIiwNCiAgICAgIEVVTklTYV8xID09ICJNQSIgfiAiTWFyaW5lIGhhYml0YXRzIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgKSwNCiAgICBFVU5JU2JfMV9kZXNjciA9IGNhc2Vfd2hlbigNCiAgICAgIEVVTklTYl8xID09ICJWIiB+ICJWZWdldGF0ZWQgbWFuLW1hZGUgaGFiaXRhdHMiLA0KICAgICAgRVVOSVNiXzEgPT0gIlUiIH4gIklubGFuZCBoYWJpdGF0cyB3aXRoIG5vIG9yIGxpdHRsZSBzb2lsIiwNCiAgICAgIEVVTklTYl8xID09ICJUIiB+ICJGb3Jlc3RzIGFuZCBvdGhlciB3b29kZWQgbGFuZCIsDQogICAgICBFVU5JU2JfMSA9PSAiUyIgfiAiSGVhdGhsYW5kcywgc2NydWIgYW5kIHR1bmRyYSIsDQogICAgICBFVU5JU2JfMSA9PSAiUiIgfiAiR3Jhc3NsYW5kcyIsDQogICAgICBFVU5JU2JfMSA9PSAiUSIgfiAiV2V0bGFuZHMiLA0KICAgICAgRVVOSVNiXzEgPT0gIlAiIH4gIklubGFuZCB3YXRlcnMiLA0KICAgICAgRVVOSVNiXzEgPT0gIk4iIH4gIkNvYXN0YWwgaGFiaXRhdHMiLA0KICAgICAgRVVOSVNiXzEgPT0gIk1BIiB+ICJNYXJpbmUgaGFiaXRhdHMiLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTY18xX2Rlc2NyID0gY2FzZV93aGVuKA0KICAgICAgRVVOSVNjXzEgPT0gIlYiIH4gIlZlZ2V0YXRlZCBtYW4tbWFkZSBoYWJpdGF0cyIsDQogICAgICBFVU5JU2NfMSA9PSAiVSIgfiAiSW5sYW5kIGhhYml0YXRzIHdpdGggbm8gb3IgbGl0dGxlIHNvaWwiLA0KICAgICAgRVVOSVNjXzEgPT0gIlQiIH4gIkZvcmVzdHMgYW5kIG90aGVyIHdvb2RlZCBsYW5kIiwNCiAgICAgIEVVTklTY18xID09ICJTIiB+ICJIZWF0aGxhbmRzLCBzY3J1YiBhbmQgdHVuZHJhIiwNCiAgICAgIEVVTklTY18xID09ICJSIiB+ICJHcmFzc2xhbmRzIiwNCiAgICAgIEVVTklTY18xID09ICJRIiB+ICJXZXRsYW5kcyIsDQogICAgICBFVU5JU2NfMSA9PSAiUCIgfiAiSW5sYW5kIHdhdGVycyIsDQogICAgICBFVU5JU2NfMSA9PSAiTiIgfiAiQ29hc3RhbCBoYWJpdGF0cyIsDQogICAgICBFVU5JU2NfMSA9PSAiTUEiIH4gIk1hcmluZSBoYWJpdGF0cyIsDQogICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXw0KICAgICksDQogICAgRVVOSVNkXzFfZGVzY3IgPSBjYXNlX3doZW4oDQogICAgICBFVU5JU2RfMSA9PSAiViIgfiAiVmVnZXRhdGVkIG1hbi1tYWRlIGhhYml0YXRzIiwNCiAgICAgIEVVTklTZF8xID09ICJVIiB+ICJJbmxhbmQgaGFiaXRhdHMgd2l0aCBubyBvciBsaXR0bGUgc29pbCIsDQogICAgICBFVU5JU2RfMSA9PSAiVCIgfiAiRm9yZXN0cyBhbmQgb3RoZXIgd29vZGVkIGxhbmQiLA0KICAgICAgRVVOSVNkXzEgPT0gIlMiIH4gIkhlYXRobGFuZHMsIHNjcnViIGFuZCB0dW5kcmEiLA0KICAgICAgRVVOSVNkXzEgPT0gIlIiIH4gIkdyYXNzbGFuZHMiLA0KICAgICAgRVVOSVNkXzEgPT0gIlEiIH4gIldldGxhbmRzIiwNCiAgICAgIEVVTklTZF8xID09ICJQIiB+ICJJbmxhbmQgd2F0ZXJzIiwNCiAgICAgIEVVTklTZF8xID09ICJOIiB+ICJDb2FzdGFsIGhhYml0YXRzIiwNCiAgICAgIEVVTklTZF8xID09ICJNQSIgfiAiTWFyaW5lIGhhYml0YXRzIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApDQpgYGANCg0KUGxvdCBmb3IgRVVOSVNhXzEgKHRoZSBmaXJzdCBhc3NpZ25lZCBFVU5JUyBpbiBjYXNlcyBvZiBtdWx0aXBsZSBhc3NpZ25hdGlvbnMsIGxldmVsIDEpOg0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhFVU5JU2FfMV9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDEiKSArIGNvb3JkX2ZsaXAoKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKCFpcy5uYShFVU5JU2FfMV9kZXNjcikpLCBhZXMoRVVOSVNhXzFfZGVzY3IpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBsZXZlbCAxIikgKyBjb29yZF9mbGlwKCkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsImlzc3VlOC50aWZmIiksDQogICAgICAgd2lkdGg9MTgsaGVpZ2h0PTEwLHVuaXRzPSJjbSIsZHBpPTMwMCkNCmBgYA0KDQojIElTU1VFIDk6IE1hbmlwdWxhdGVkIHBsb3RzIGFuZCBpbmZvIG9uIG1hbmlwdWxhdGlvbiB0eXBlDQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKGBNYW5pcHVsYXRlICh5L24pYCkpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIk1hbmlwdWxhdGlvbiIpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJpc3N1ZTkudGlmZiIpLA0KICAgICAgIHdpZHRoPTEwLGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmBgYA0KDQpMaXN0IG9mIFR5cGUgb2YgTWFuaXB1bGF0aW9uIGluIG1hbmlwdWxhdGVkIHBsb3RzIChtaXhlZCBpbmZvcm1hdGlvbik6DQoNCmBgYHtyfQ0Kd3JpdGVfY3N2KGRhdGEuZnJhbWUodW5pcXVlKGRiX3Jlc3VydiRgVHlwZSBvZiBtYW5pcHVsYXRpb25gKSksDQogICAgICAgICAgaGVyZSgib3V0cHV0IiwgImNzdiIsImlzc3VlOS5jc3YiKSkNCmBgYA0KDQojIElTU1VFIDEwOiBMb2NhdGlvbiBtZXRob2QNCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoYExvY2F0aW9uIG1ldGhvZGApKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJMb2NhdGlvbiBtZXRob2QiKSArIGNvb3JkX2ZsaXAoKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWUxMC50aWZmIiksDQogICAgICAgd2lkdGg9MTgsaGVpZ2h0PTgsdW5pdHM9ImNtIixkcGk9MzAwKQ0KYGBgDQoNCiMgSVNTVUUgMTE6IFJlc3VydmV5IHByb2plY3QgdHlwZXMNCg0KYGBge3J9DQp1bmlxdWUoZGJfcmVzdXJ2JFJTX1BST0pUWVApDQpgYGANCg0KVW5pZnkgY29kZXM6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2IDwtIGRiX3Jlc3VydiAlPiUNCiAgbXV0YXRlKFJTX1BST0pUWVAgPSByZWNvZGUoUlNfUFJPSlRZUCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJlc2FtcGxpbmciID0gInJlc2FtcGxpbmciLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUGVybWFuZW50IChtYW4pIiA9ICJwZXJtYW5lbnQgKG1hbikiKSkNCmBgYA0KDQpgYGB7cn0NCnVuaXF1ZShkYl9yZXN1cnYkUlNfUFJPSlRZUCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhSU19QUk9KVFlQLCBmaWxsPWBNYW5pcHVsYXRlICh5L24pYCkpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIlJlc3VydmV5IHByb2plY3QgdHlwZSIpICsgY29vcmRfZmxpcCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJpc3N1ZTExLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xOCxoZWlnaHQ9OCx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KIyBJU1NVRSAxMjogQ29sdW1uIFJTX0RVUEwNCg0KYGBge3J9DQpkYl9yZXN1cnYgJT4lIGZpbHRlcighaXMubmEoUlNfRFVQTCkpICU+JSBzZWxlY3QoUlNfQ09ERSwgUlNfRFVQTCkgJT4lDQogIGRpc3RpbmN0KCkNCmBgYA0KDQojIElTU1VFIDEzOiBMb2NhdGlvbiB1bmNlcnRhaW50eQ0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogICMgUmVkZWZpbmUgcHJlY2lzaW9uX25ldywgd2hpY2ggd2FzIHdyb25nDQogIG11dGF0ZShwcmVjaXNpb25fbmV3ID0gZmFjdG9yKGlmZWxzZShpcy5uYShMb25fcHJlYykgJiBpcy5uYShMYXRfcHJlYyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAwLCAxKSkpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoYExvY2F0aW9uIHVuY2VydGFpbnR5IChtKWAsIGZpbGwgPSBwcmVjaXNpb25fbmV3KSkgKw0KICBnZW9tX2hpc3RvZ3JhbSggY29sb3IgPSAiYmxhY2siKSArDQogIHhsYWIoIkxvY2F0aW9uIHVuY2VydGFpbnR5IChtKSIpDQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoYExvY2F0aW9uIHVuY2VydGFpbnR5IChtKWAgPD0gNTAwKSwNCiAgICAgICBhZXMoYExvY2F0aW9uIHVuY2VydGFpbnR5IChtKWAsIGZpbGwgPSBwcmVjaXNpb25fbmV3KSkgKw0KICBnZW9tX2hpc3RvZ3JhbShjb2xvciA9ICJibGFjayIpICsNCiAgeGxhYigiTG9jYXRpb24gdW5jZXJ0YWludHkgKG0pIDw9IDUwMCIpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJpc3N1ZTEzXzEudGlmZiIpLA0KICAgICAgIHdpZHRoPTE4LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihgTG9jYXRpb24gdW5jZXJ0YWludHkgKG0pYCA+IDUwMCksDQogICAgICAgYWVzKGBMb2NhdGlvbiB1bmNlcnRhaW50eSAobSlgLCBmaWxsID0gcHJlY2lzaW9uX25ldykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAiYmxhY2siKSArDQogIHhsYWIoIkxvY2F0aW9uIHVuY2VydGFpbnR5IChtKSA+IDUwMCIpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJpc3N1ZTEzXzIudGlmZiIpLA0KICAgICAgIHdpZHRoPTE4LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmBgYA0KDQoNCiMgTk8gSVNTVUVTIEZST00gSEVSRQ0KDQojIEFsdGl0dWRlIGFuZCBzbG9wZSB2YWx1ZXMNCg0KVW5pcXVlIHNsb3BlIHZhbHVlczoNCg0KYGBge3J9DQp1bmlxdWUoKGRiX3Jlc3VydikkYFNsb3BlICjCsClgKSAlPiUgc3RyX3NvcnQoKQ0KYGBgDQoNClNldCBhbHRpdHVkZSwgc2xvcGUgYW5kIGFzcGVjdCBhcyBudW1lcmljOg0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogIG11dGF0ZSgNCiAgICAjIFNvbWUgYWx0aXR1ZGUgdmFsdWVzIGhhdmUgYSAiLSIgYWZ0ZXIgdGhlIG51bWJlciwNCiAgICAjIGNvbnZlcnQgdG8gbnVtZXJpYyBhZnRlciByZW1vdmluZyB0aGF0DQogICAgQWx0aXR1ZGUgPSBhcy5udW1lcmljKGdzdWIoIi0iLCAiIiwgQWx0aXR1ZGUpKSwNCiAgICAjIFNvbWUgc2xvcGUgdmFsdWVzIGFyZSBub3RlZCBhcyAiXyIgb3IgIi0iLCB0aGVzZSBzaG91bGQgYmUgTkEsDQogICAgIyBvdGhlcndpc2UgY29udmVydCB0byBudW1lcmljDQogICAgYFNsb3BlICjCsClgID0gaWZlbHNlKGBTbG9wZSAowrApYCA9PSAiXyIgfCBgU2xvcGUgKMKwKWAgPT0gIi0iLA0KICAgICAgICAgICAgICAgICAgIE5BLCBhcy5udW1lcmljKGBTbG9wZSAowrApYCkpLA0KICAgICMgQ29udmVydCBhc3BlY3QgdmFsdWVzIHRvIG51bWVyaWMNCiAgICBgQXNwZWN0ICjCsClgID0gYXMubnVtZXJpYyhgQXNwZWN0ICjCsClgKQ0KICAgICkNCmBgYA0KDQpIaXN0b2dyYW1zOg0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhBbHRpdHVkZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikNCmdncGxvdChkYl9yZXN1cnYsIGFlcyhgQXNwZWN0ICjCsClgKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKGBTbG9wZSAowrApYCkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsImlzc3VlOC50aWZmIiksDQogICAgICAgd2lkdGg9MTgsaGVpZ2h0PTEwLHVuaXRzPSJjbSIsZHBpPTMwMCkNCmBgYA0KDQpgYGB7cn0NCnJhbmdlKGRiX3Jlc3VydiRgU2xvcGUgKMKwKWAsIG5hLnJtPVQpDQpgYGANCg0KIyBBZGQgY29sdW1ucyBkYXRlIGFuZCB5ZWFyDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2IDwtIGRiX3Jlc3VydiAlPiUNCiAgbXV0YXRlKGRhdGUgPSBkbXkoYERhdGUgb2YgcmVjb3JkaW5nYCksIHllYXIgPSB5ZWFyKGRhdGUpKQ0KYGBgDQoNCkhpc3RvZ3JhbXM6DQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKHllYXIpKSArIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpDQpgYGANCg0KIyBQbG90IHNpemUNCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoYFJlbGV2w6kgYXJlYSAobcKyKWApKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpDQpgYGANCg0KT2JzZXJ2YXRpb25zIHdpdGggbm8gaW5mbyBvbiBwbG90IHNpemU6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnYgJT4lIGZpbHRlcihpcy5uYShgUmVsZXbDqSBhcmVhIChtwrIpYCkpKQ0KYGBgDQoNCiMgQ292ZXIgdmFsdWVzICh0b3RhbCwgdHJlZXMsIHNocnVicywgaGVyYnMsIG1vc3Nlc3MpDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2ICU+JQ0KICBwaXZvdF9sb25nZXIoY29scyA9IGMoYENvdmVyIHRvdGFsICglKWAsIGBDb3ZlciB0cmVlIGxheWVyICglKWAsDQogICAgICAgICAgICAgICAgICAgICAgICBgQ292ZXIgc2hydWIgbGF5ZXIgKCUpYCwgYENvdmVyIGhlcmIgbGF5ZXIgKCUpYCwNCiAgICAgICAgICAgICAgICAgICAgICAgIGBDb3ZlciBtb3NzIGxheWVyICglKWApLA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUpKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIsIGJpbnMgPSAxMCkgKw0KICBmYWNldF93cmFwKH4gdmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlIikgKw0KICBsYWJzKHggPSAiVmFsdWUiLCB5ID0gIkZyZXF1ZW5jeSIpDQpgYGANCg0KYGBge3J9DQpkYl9yZXN1cnYgJT4lDQogIHJlZnJhbWUoYWNyb3NzKGMoYENvdmVyIHRvdGFsICglKWAsIGBDb3ZlciB0cmVlIGxheWVyICglKWAsDQogICAgICAgICAgICAgICAgICAgICBgQ292ZXIgc2hydWIgbGF5ZXIgKCUpYCwgYENvdmVyIGhlcmIgbGF5ZXIgKCUpYCwNCiAgICAgICAgICAgICAgICAgICAgIGBDb3ZlciBtb3NzIGxheWVyICglKWApLCB+cmFuZ2UoLiwgbmEucm0gPSBUUlVFKSkpDQpgYGANCg0KQWxsIHZhbHVlcyBPSy4NCg0KIyBNb3NzZXMgYW5kIGxpY2hlbnMgaWRlbnRpZmllZA0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhgTW9zc2VzIGlkZW50aWZpZWQgKFkvTilgKSkgKyBnZW9tX2JhcigpDQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoYExpY2hlbnMgaWRlbnRpZmllZCAoWS9OKWApKSArIGdlb21fYmFyKCkNCmBgYA0KDQpOQSBpbiBtb3N0IGNhc2VzLg0KDQojIEFsbCByZXN1cnZleXMgZm9yIGVhY2ggcmVzdXJ2ZXkgcGxvdCB0byBzZW5kIHRvIEJlYQ0KDQpgYGB7cn0NCmRiX0V1cm9wYTwtIGRiX3Jlc3VydiAlPiUNCiAgZ3JvdXBfYnkoUlNfQ09ERSwgYFJlU3VydmV5IHNpdGVgLA0KICAgICAgICAgICAjIElmIFJlU3VydmV5IHBsb3QgaXMgbm90IE5BLCANCiAgICAgICAgICAgIyBncm91cCBieSBSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YA0KICAgICAgICAgICBgUmVTdXJ2ZXkgcGxvdGAgPSBpZmVsc2UoaXMubmEoYFJlU3VydmV5IHBsb3RgKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOQV9jaGFyYWN0ZXJfLCBgUmVTdXJ2ZXkgcGxvdGApLA0KICAgICAgICAgICAjIElmIFJlU3VydmV5IHBsb3QgaXMgTkEsIGdyb3VwIGJ5IGNvb3JkaW5hdGVzDQogICAgICAgICAgICMgQ3JlYXRlIGEgdW5pcXVlIGdyb3VwaW5nIHZhcmlhYmxlIHRoYXQgdXNlcyBjb29yZGluYXRlcw0KICAgICAgICAgICAjIG9ubHkgd2hlbiBjb25kaXRpb25zIGFyZSBtZXQNCiAgICAgICAgICAgZ3JvdXBfY29vcmRzID0gaWZlbHNlKGlzLm5hKGBSZVN1cnZleSBwbG90YCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShMb25fdXBkYXRlZCwgTGF0X3VwZGF0ZWQpLCBOQV9jaGFyYWN0ZXJfKQ0KICApICU+JQ0KICAjIEFkZCB1bmlxdWUgaWRlbnRpZmllcnMgZm9yIGVhY2ggcGxvdC4NCiAgIyBUaGVzZSBhcmUgYmFzZWQgb24gdGhlIHVuaXF1ZSBjb21iaW5hdGlvbiBvZiBSU19DT0RFLCBSZVN1cnZleSBzaXRlIGFuZCANCiAgIyBSZVN1cnZleSBwbG90IChXaGVuIFJlU3VydmV5IHBsb3QgaXMgbm90IE5BKQ0KICAjIGFuZCBvbiB0aGUgdW5pcXVlIGNvbWJpbmF0aW9uIG9mIFJTX0NPREUsIFJlU3VydmV5IHNpdGUgDQogICMgYW5kIHVwZGF0ZWQgY29vcmRpbmF0ZXMgKFdoZW4gUmVTdXJ2ZXkgcGxvdCBpcyBOQSkNCiAgbXV0YXRlKHBsb3RfdW5pcXVlX2lkID0gY3VyX2dyb3VwX2lkKCkpICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIENvdW50cnksIGBEYXRlIG9mIHJlY29yZGluZ2AsIFJTX0NPREUsDQogICAgICAgICBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCwgTG9uX3VwZGF0ZWQsIExhdF91cGRhdGVkLA0KICAgICAgICAgZ3JvdXBfY29vcmRzLCBgRXhwZXJ0IFN5c3RlbWAsIGBMb2NhdGlvbiBtZXRob2RgLCBwbG90X3VuaXF1ZV9pZCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgIyBDb252ZXJ0IGRhdGVzIHRvIGRhdGUgZm9ybWF0IGFuZCBnZXQgdGhlIHllYXINCiAgbXV0YXRlKGRhdGUgPSBkbXkoYERhdGUgb2YgcmVjb3JkaW5nYCksIHllYXIgPSB5ZWFyKGRhdGUpKSAlPiUNCiAgc2VsZWN0KC1gRGF0ZSBvZiByZWNvcmRpbmdgLCAtZGF0ZSwgLWdyb3VwX2Nvb3JkcykgJT4lDQogICMgQWRkIHVuaXF1ZSBpZGVudGlmaWVycyBmb3IgZWFjaCBvYnNlcnZhdGlvbg0KICBtdXRhdGUob2JzX3VuaXF1ZV9pZCA9IHJvd19udW1iZXIoKSkNCmBgYA0KDQpgYGB7cn0NCnByaW50KGRiX0V1cm9wYSwgd2lkdGggPSBJbmYpDQpgYGANCg0KU2F2ZSB0byBjc3YgKGZpbGUgZm9yIHVzKToNCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfRXVyb3BhLGhlcmUoImRhdGEiLCAiY2xlYW4iLCJkYl9FdXJvcGFfMjAyNTAxMDcuY3N2IikpDQpgYGANCg0KU2F2ZSB0byBjc3YgKGZpbGUgZm9yIEJlYSwgd2l0aCBvbmx5IGVzc2VudGlhbCBpbmZvKToNCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfRXVyb3BhICU+JSANCiAgICAgICAgICAgIHNlbGVjdChvYnNfdW5pcXVlX2lkLCBwbG90X3VuaXF1ZV9pZCwgTG9uX3VwZGF0ZWQsIExhdF91cGRhdGVkLA0KICAgICAgICAgICAgICAgICAgIHllYXIpLA0KICAgICAgICAgIGhlcmUoImRhdGEiLCAiY2xlYW4iLCJkYl9FdXJvcGFfMjAyNDEyMTBfc2hvcnQuY3N2IikpDQpgYGANCg0KIyBJbmZvIG9uIEhhYml0YXRJRCBmcm9tIERLIA0KDQpCYXNlZCBvbiBpbmZvcm1hdGlvbiBnb3QgZnJvbSBKZXNwZXIuDQoNCiMjIFJlYWQgdGhlIGRhdGEgc2VudCBieSBKZXNwZXIgZnJvbSBESw0KDQpgYGB7cn0NCmRiX0RLX0o8LXJlYWRfdHN2KGhlcmUoImRhdGEiLCAicmF3IiwNCiAgICAgICAgICAgICAgICAgICAgICAgIkRLX05hdHVyZGF0YV9SZXNfaGFiaXRhdF9oYWJfY29kZXNfSmVzcGVyIiwNCiAgICAgICAgICAgICAgICAgICJES19OYXR1cmRhdGFfUmVzX2hhYml0YXRfaGFiX2NvZGVzLnR4dCIpKQ0KYGBgDQoNCiMjIEFkZCBpbmZvIG9uIEhhYml0YXRJRCB0byBkYl9yZXN1cnYNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICAjIEtlZXBpbmcgYWxsIG9icyBpbiBkYl9yZXN1cnYgYnV0IG5vdCBhbGwgaW4gZGJfREtfSg0KICBsZWZ0X2pvaW4oZGJfREtfSiAlPiUgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBIYWJpdGF0SUQpKQ0KYGBgDQoNCiMjIExpc3Qgb2YgSGFiaXRhdElEDQoNCmBgYHtyfQ0KcHJpbnQoZGJfcmVzdXJ2ICU+JSBkaXN0aW5jdChIYWJpdGF0SUQpLCBuID0gMTAwKQ0KYGBgDQoNCldyaXRlIGNzdjoNCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfcmVzdXJ2ICU+JSBkaXN0aW5jdChIYWJpdGF0SUQpLA0KICAgICAgICAgIGhlcmUoImRhdGEiLCAiY2xlYW4iLCJsaXN0X0hhYml0YXRJRF9ES19yZXN1cnYuY3N2IikpDQpgYGANCg0KIyMgQ2FzZXMgd2l0aG91dCBIYWJpdGF0SUQgaW5mbw0KDQpDYXNlcyB3aXRob3V0IEVTeSBFVU5JUyBoYWJpdGF0Og0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoaXMubmEoYEV4cGVydCBTeXN0ZW1gKSkpL25yb3coZGJfcmVzdXJ2KQ0KYGBgDQoNCkNhc2VzIHdpdGhvdXQgRVN5IEVVTklTIGhhYml0YXQgYnV0IHdpdGggSGFiaXRhdElEIGZyb20gREs6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnYgJT4lIGZpbHRlcihpcy5uYShgRXhwZXJ0IFN5c3RlbWApJiFpcy5uYShIYWJpdGF0SUQpKSkvbnJvdyhkYl9yZXN1cnYpDQpgYGANCg0KQ2FzZXMgd2l0aG91dCBFU3kgRVVOSVMgaGFiaXRhdCBhbmQgd2l0aG91dCBIQUJJVEFUIGZyb20gREs6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnYgJT4lIA0KICAgICAgIGZpbHRlcihpcy5uYShgRXhwZXJ0IFN5c3RlbWApJmlzLm5hKEhhYml0YXRJRCkpKS9ucm93KGRiX3Jlc3VydikNCmBgYA0KDQpDYXNlcyB3aXRob3V0IEVTeSBFVU5JUyBoYWJpdGF0IGFuZCB3aXRob3V0IEhhYml0YXRJRCBmcm9tIERLIHdoZXJlIGRhdGEgaXMgcHJlc2VuY2UgLyBhYnNlbmNlOg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2ICU+JQ0KICAgICAgIGZpbHRlcihpcy5uYShgRXhwZXJ0IFN5c3RlbWApICYNCiAgICAgICAgICAgICAgICBpcy5uYShIYWJpdGF0SUQpICYNCiAgICAgICAgICAgICAgICBgQ292ZXIgYWJ1bmRhbmNlIHNjYWxlYCA9PSAiUHJlc2VuY2UvQWJzZW5jZSIpKS8NCiAgbnJvdyhkYl9yZXN1cnYpDQpgYGANCg0KQ2FzZXMgd2l0aG91dCBFU3kgRVVOSVMgaGFiaXRhdCBhbmQgd2l0aG91dCBIYWJpdGF0SUQgZnJvbSBESyB3aGVyZSBkYXRhIGlzIG5vdCBwcmVzZW5jZSAvIGFic2VuY2U6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnYgJT4lDQogICAgICAgZmlsdGVyKGlzLm5hKGBFeHBlcnQgU3lzdGVtYCkgJg0KICAgICAgICAgICAgICAgIGlzLm5hKEhhYml0YXRJRCkgJg0KICAgICAgICAgICAgICAgIGBDb3ZlciBhYnVuZGFuY2Ugc2NhbGVgICE9ICJQcmVzZW5jZS9BYnNlbmNlIikpLw0KICBucm93KGRiX3Jlc3VydikNCmBgYA0KDQojIyBDaGFuZ2Ugc29tZSBBbm5leCBJIGhhYml0YXQgY29kZXMgdGhhdCB3ZXJlIHdyb25nDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2IDwtIGRiX3Jlc3VydiAlPiUNCiAgbXV0YXRlKEhhYml0YXRJRCA9IGFzLmNoYXJhY3RlcihIYWJpdGF0SUQpKSAlPiUNCiAgbXV0YXRlKEhhYml0YXRJRCA9IGlmZWxzZShIYWJpdGF0SUQgPT0gIjk5OTgiLCAiOTFEMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKEhhYml0YXRJRCA9PSAiOTk5OSIsICI5MUUwIiwgSGFiaXRhdElEKSkpDQpgYGANCg0KIyBBZGQgaW5mbyBvbiBjb3JyZXNwb25kZW5jZXMgSGFiaXRhdElEIChESywgSmVzcGVyKSAtIEVVTklTDQoNClJlYWQgY29ycmVzcG9uZGVuY2VzIGZpbGU6DQoNCmBgYHtyfQ0KY29ycmVzcG9uZGVuY2VzPC1yZWFkX2V4Y2VsKGhlcmUoImRhdGEiLCAiZWRpdGVkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjb3JyZXNwb25kZW5jZV9IYWJpdGF0SURfREsueGxzeCIpKQ0KYGBgDQoNCkFkZCBpbmZvIHRvIGRiX3Jlc3VydjoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICAjIEtlZXBpbmcgYWxsIG9icyBpbiBkYl9yZXN1cnYgYnV0IG5vdCBhbGwgaW4gZGJfREtfSg0KICBsZWZ0X2pvaW4oY29ycmVzcG9uZGVuY2VzICU+JSBzZWxlY3QoSGFiaXRhdElELCBFVU5JUykpDQpgYGANCg0KQ29ycmVjdCBOQSB2YWx1ZXMgaW4gRVVOSVMNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoRVVOSVMgPSBpZmVsc2UoRVVOSVMgPT0gIk5BIiwgTkEsIEVVTklTKSkNCmBgYA0KDQpBZGQgaW5mbyBvbiBFVU5JUyAoREspIHRvIEVVTklTYToNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoRVVOSVNhID0NCiAgICAgICAgICAgIyBJZiBFVU5JUyAoREspIGlzIGF2YWlsYWJsZSwgYWRkIGFzIEVVTklTYQ0KICAgICAgICAgICBpZmVsc2UoIWlzLm5hKEVVTklTKSwgRVVOSVMsIA0KICAgICAgICAgICAgICAgICAgIyBPdGhlcndpc2Uga2VlcCBFVU5JU2ENCiAgICAgICAgICAgICAgICAgIEVVTklTYSksDQogICAgICAgICBFVU5JU19hc3NpZ25hdGlvbiA9IGlmZWxzZSghaXMubmEoRVVOSVMpLCAiSW5mbyBmcm9tIERLIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShpcy5uYShFVU5JU2EpLCAiTm90IHBvc3NpYmxlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXhwZXJ0IHN5c3RlbSIpKSkgJT4lDQogICMgUmVtb3ZlIGNvbHVtbiBFVU5JUyAoREspDQogIHNlbGVjdCgtRVVOSVMpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoRVVOSVNfYXNzaWduYXRpb24pKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBhc3NpZ25hdGlvbiIpDQpgYGANCg0KIyMgR2VuZXJhdGUgZmlsZSBmb3IgSVNTVUUgNQ0KDQpgYGB7cn0NCndyaXRlX2NzdihkYl9yZXN1cnYgJT4lDQogICAgICAgICAgICBmaWx0ZXIoaXMubmEoYEV4cGVydCBTeXN0ZW1gKSZpcy5uYShIYWJpdGF0SUQpKSwNCiAgICAgICAgICBoZXJlKCJvdXRwdXQiLCAiY3N2IiwiaXNzdWU1LmNzdiIpKQ0KYGBgDQoNCiMjIFVwZGF0ZSBjb2x1bW5zIGZvciBFVU5JUyBsZXZlbHMgYW5kIGRlc2NyaXB0aW9ucw0KDQpVcGRhdGUgdGhlIGNvbHVtbnMgZm9yIHRoZSBkaWZmZXJlbnQgRVVOSVNzIGxldmVsczoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoDQogICAgIyBFVU5JU2EgbGV2ZWxzDQogICAgRVVOSVNhXzEgPSBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCAyLCAxKSksDQogICAgRVVOSVNhXzIgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2EpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYSwgIk1BIiksIDMsIDIpLCANCiAgICAgIHN1YnN0cihFVU5JU2EsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYSwgIk1BIiksIDMsIDIpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTYV8zID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNhKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA0LCAzKSwgDQogICAgICBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA0LCAzKSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgICApLA0KICAgIEVVTklTYV80ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNhKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApICU+JQ0KICAjIFJlbW92ZSBIYWJpdGF0SUQgY29sdW1uDQogIHNlbGVjdCgtSGFiaXRhdElEKQ0KYGBgDQoNClVwZGF0ZSBjb2x1bW5zIHdpdGggZGVzY3JpcHRpb25zIGZvciB0aGUgbGV2ZWwgMSBjb2RlczoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoDQogICAgRVVOSVNhXzFfZGVzY3IgPSBjYXNlX3doZW4oDQogICAgICBFVU5JU2FfMSA9PSAiViIgfiAiVmVnZXRhdGVkIG1hbi1tYWRlIGhhYml0YXRzIiwNCiAgICAgIEVVTklTYV8xID09ICJVIiB+ICJJbmxhbmQgaGFiaXRhdHMgd2l0aCBubyBvciBsaXR0bGUgc29pbCIsDQogICAgICBFVU5JU2FfMSA9PSAiVCIgfiAiRm9yZXN0cyBhbmQgb3RoZXIgd29vZGVkIGxhbmQiLA0KICAgICAgRVVOSVNhXzEgPT0gIlMiIH4gIkhlYXRobGFuZHMsIHNjcnViIGFuZCB0dW5kcmEiLA0KICAgICAgRVVOSVNhXzEgPT0gIlIiIH4gIkdyYXNzbGFuZHMiLA0KICAgICAgRVVOSVNhXzEgPT0gIlEiIH4gIldldGxhbmRzIiwNCiAgICAgIEVVTklTYV8xID09ICJQIiB+ICJJbmxhbmQgd2F0ZXJzIiwNCiAgICAgIEVVTklTYV8xID09ICJOIiB+ICJDb2FzdGFsIGhhYml0YXRzIiwNCiAgICAgIEVVTklTYV8xID09ICJNQSIgfiAiTWFyaW5lIGhhYml0YXRzIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApDQpgYGANCg0KIyMgTnVtYmVyIG9mIGRpZmZlcmVudCBFVU5JUyBjb2Rlcw0KDQpSZWNhbGN1bGF0ZSBob3cgbWFueSBkaWZmZXJlbnQgRVVOSVMgY29kZXMgaGF2ZSBiZWVuIGFzc2lnbmVkOg0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogIG11dGF0ZSgNCiAgICAjIENvdW50IHRoZSBudW1iZXIgb2Ygbm9uLU5BIHZhbHVlcyBhY3Jvc3MgdGhlIEVVTklTIGNvbHVtbnMNCiAgICBuX0VVTklTID0gcm93U3VtcyghaXMubmEoc2VsZWN0KC4sIEVVTklTYTpFVU5JU2QpKSkNCiAgKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKG5fRVVOSVMpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJOdW1iZXIgb2YgZGlmZmVybnQgRVVOSVMgY29kZXMgYXNzaWduZWQiKSArIGNvb3JkX2ZsaXAoKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKG5fRVVOSVMgPiAwKSwgYWVzKG5fRVVOSVMpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJOdW1iZXIgb2YgZGlmZmVybnQgRVVOSVMgY29kZXMgYXNzaWduZWQiKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCk5ldyBwbG90IGZvciBFVU5JU2FfMSAodGhlIGZpcnN0IGFzc2lnbmVkIEVVTklTIGluIGNhc2VzIG9mIG11bHRpcGxlIGFzc2lnbmF0aW9ucywgbGV2ZWwgMSk6DQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKEVVTklTYV8xX2Rlc2NyKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiRVVOSVMgbGV2ZWwgMSIpICsgY29vcmRfZmxpcCgpDQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoIWlzLm5hKEVVTklTYV8xX2Rlc2NyKSksIGFlcyhFVU5JU2FfMV9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDEiKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCiMgQWRkIGluZm8gb24gZGVzY3JpcHRpb25zIGZvciBFVU5JUyBsZXZlbHMgMi00DQoNCmBgYHtyfQ0KZGVzY3JpcHRpb25zPC1yZWFkX2V4Y2VsKGhlcmUoImRhdGEiLCAiZWRpdGVkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFVU5JUy1IYWJpdGF0cy0yMDIxLTA2LTAxX21vZGlmaWVkLnhsc3giKSkNCmBgYA0KDQpgYGB7cn0NCiMgRGVmaW5lIHRoZSBjb2x1bW5zIGFuZCBjb3JyZXNwb25kaW5nIGRlc2NyaXB0aW9uIGNvbHVtbiBuYW1lcw0KZXVuaXNfY29scyA8LSBjKCJFVU5JU2FfMiIsICJFVU5JU2FfMyIsICJFVU5JU2FfNCIsDQogICAgICAgICAgICAgICAgIkVVTklTYl8yIiwgIkVVTklTYl8zIiwgIkVVTklTYl80IiwgDQogICAgICAgICAgICAgICAgIkVVTklTY18yIiwgIkVVTklTY18zIiwgIkVVTklTY180IiwNCiAgICAgICAgICAgICAgICAiRVVOSVNkXzIiLCAiRVVOSVNkXzMiLCAiRVVOSVNkXzQiKQ0KDQojIENyZWF0ZSBjb3JyZXNwb25kaW5nIGRlc2NyaXB0aW9uIGNvbHVtbiBuYW1lcw0KZGVzY3JfY29sX25hbWVzIDwtIHBhc3RlMChldW5pc19jb2xzLCAiX2Rlc2NyIikNCg0KIyBVc2UgcmVkdWNlIHRvIGxvb3AgdGhyb3VnaCB0aGUgY29sdW1ucyBhbmQgam9pbiBkeW5hbWljYWxseSBiYXNlZCBvbiBsZXZlbA0KZGJfcmVzdXJ2IDwtIHJlZHVjZShzZXFfYWxvbmcoZXVuaXNfY29scyksIGZ1bmN0aW9uKGRhdGEsIGkpIHsNCiAgIyBFeHRyYWN0IGxldmVsIG51bWJlciBmcm9tIHRoZSBjb2x1bW4gbmFtZSAoZS5nLiwgRVVOSVNhXzIgLT4gMikNCiAgbGV2ZWwgPC0gYXMubnVtZXJpYyhnc3ViKCJcXEQiLCAiIiwgZXVuaXNfY29sc1tpXSkpDQogIA0KICAjIEZpbHRlciBkZXNjcmlwdGlvbnMgZm9yIHRoZSBjb3JyZXNwb25kaW5nIGxldmVsDQogIGRlc2NyaXB0aW9uc19sZXZlbCA8LSBkZXNjcmlwdGlvbnMgJT4lDQogICAgZmlsdGVyKGxldmVsID09IGxldmVsKSAlPiUNCiAgICBzZWxlY3QoYEVVTklTIDIwMjAgY29kZWAsIGBFVU5JUy0yMDIxIGhhYml0YXQgbmFtZWApDQogIA0KICAjIFBlcmZvcm0gdGhlIGxlZnRfam9pbiBhbmQgcmVuYW1lIHRoZSBjb2x1bW4gZHluYW1pY2FsbHkNCiAgZGF0YSAlPiUNCiAgICBsZWZ0X2pvaW4oDQogICAgICBkZXNjcmlwdGlvbnNfbGV2ZWwsDQogICAgICBieSA9IHNldE5hbWVzKCJFVU5JUyAyMDIwIGNvZGUiLCBldW5pc19jb2xzW2ldKQ0KICAgICkgJT4lDQogICAgcmVuYW1lKCEhZGVzY3JfY29sX25hbWVzW2ldIDo9IGBFVU5JUy0yMDIxIGhhYml0YXQgbmFtZWApDQp9LCAuaW5pdCA9IGRiX3Jlc3VydikNCmBgYA0KDQpUaGUgbWF0Y2hpbmcgZGlkIG5vdCB3b3JrIHNvbWV0aW1lcywgY29ycmVjdCENCg0KYGBge3J9DQojIENvcnJlY3QgRVVOSVNhIGxldmVscyAyLTQgZGVzY3JpcHRpb25zDQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoRVVOSVNhXzJfZGVzY3IgPSANCiAgICAgICAgICAgaWZlbHNlKCFpcy5uYShFVU5JU2FfMl9kZXNjciksIEVVTklTYV8yX2Rlc2NyLA0KICAgICAgICAgICAgICAgICAgY2FzZV93aGVuKA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUGYiIH4gIkZyZXNoLXdhdGVyIHN1Ym1lcmdlZCB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0gIlBqIiB+ICJTdG9uZXdvcnQgdmVnZXRhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJSNCIgfiAiQWxwaW5lIGFuZCBzdWJhbHBpbmUgZ3Jhc3NsYW5kcyIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJQYiIgfiAiQ2FsY2FyZW91cyBzcHJpbmcgYW5kIHNwcmluZyBicm9vayIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJRYiIgfiAiV2V0bGFuZHMiLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUjMiIH4gIlNlYXNvbmFsbHkgd2V0IGFuZCB3ZXQgZ3Jhc3NsYW5kcyIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJRYSIgfiAiTWlyZXMiLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUGEiIH4gIkJhc2UtcG9vciBzcHJpbmcgYW5kIHNwcmluZyBicm9vayIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJQaCIgfiAiT2xpZ290cm9waGljLXdhdGVyIHZlZ2V0YXRpb24iLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUGciIH4gIkZyZXNoLXdhdGVyIG55bXBoYWVpZCB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0NCiAgICAgICAgICAgICAgICAgICAgICAiUGQiIH4gIkZyZXNoLXdhdGVyIHNtYWxsIHBsZXVzdG9waHl0ZSB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0gIlBjIiB+ICJCcmFja2lzaC13YXRlciB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0NCiAgICAgICAgICAgICAgICAgICAgICAiUGUiIH4gIkZyZXNoLXdhdGVyIGxhcmdlIHBsZXVzdG9waHl0ZSB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0gIlBpIiB+ICJEeXN0cm9waGljLXdhdGVyIHZlZ2V0YXRpb24iLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUzEiIH4gIlR1bmRyYSIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09DQogICAgICAgICAgICAgICAgICAgICAgIlU3IiB+ICJVbnZlZ2V0YXRlZCBvciBzcGFyc2VseSB2ZWdldGF0ZWQgZ3JhdmVsIGJhcnMiLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUTYiIH4gIlBlcmlvZGljYWxseSBleHBvc2VkIHNob3JlcyIsDQogICAgICAgICAgICAgICAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKQ0KICAgICAgICAgICAgICAgICAgKSwNCiAgICAgICAgIEVVTklTYV8zX2Rlc2NyID0gDQogICAgICAgICAgIGlmZWxzZSghaXMubmEoRVVOSVNhXzNfZGVzY3IpLCBFVU5JU2FfM19kZXNjciwNCiAgICAgICAgICAgICAgICAgIGNhc2Vfd2hlbigNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzMgPT0iVTcxIiB+ICJVbnZlZ2V0YXRlZCBvciBzcGFyc2VseSB2ZWdldGF0ZWQgZ3JhdmVsIGJhciBpbiBtb250YW5lIGFuZCBhbHBpbmUgcmVnaW9ucyIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8zID09IlE2MSIgfiAiUGVyaW9kaWNhbGx5IGV4cG9zZWQgc2hvcmUgd2l0aCBzdGFibGUsIGV1dHJvcGhpYyBzZWRpbWVudHMgd2l0aCBwaW9uZWVyIG9yIGVwaGVtZXJhbCB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzMgPT0iUTYyIiB+ICJQZXJpb2RpY2FsbHkgZXhwb3NlZCBzaG9yZSB3aXRoIHN0YWJsZSwgbWVzb3Ryb3BoaWMgc2VkaW1lbnRzIHdpdGggcGlvbmVlciBvciBlcGhlbWVyYWwgdmVnZXRhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgICAgICAgICAgICAgICAgICkpDQogICAgICAgICApDQpgYGANCg0KYGBge3J9DQojIENvcnJlY3QgRVVOSVNiIGxldmVscyAyLTQgZGVzY3JpcHRpb25zDQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoRVVOSVNiXzJfZGVzY3IgPSANCiAgICAgICAgICAgaWZlbHNlKCFpcy5uYShFVU5JU2JfMl9kZXNjciksIEVVTklTYl8yX2Rlc2NyLA0KICAgICAgICAgICAgICAgICAgY2FzZV93aGVuKA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2JfMiA9PSAiUGoiIH4gIlN0b25ld29ydCB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNiXzIgPT0gIlI0IiB+ICJBbHBpbmUgYW5kIHN1YmFscGluZSBncmFzc2xhbmRzIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNiXzIgPT0gIlBmIiB+ICJGcmVzaC13YXRlciBzdWJtZXJnZWQgdmVnZXRhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKQ0KICAgICAgICAgICAgICAgICAgKQ0KICAgICAgICAgKQ0KYGBgDQoNCkVVTklTYyBhbmQgRVVOSVNkIGxldmVscyAyLTQgYXJlIE9LLg0KIA0KIyBOb3RlcyBFVU5JUyBjb2RlcyAtIHRvIGNoYW5nZT8NCg0KaHR0cHM6Ly93d3cuc2NpLm11bmkuY3ovYm90YW55L2NoeXRyeS9TY2hhbWluZWVfZXRhbDIwMjFfRUVBLVJlcG9ydC1BcXVhdGljLVdldGxhbmQtaGFiaXRhdHMucGRmDQoNCkVVTklTYV8yID09ICJRNiIgOiAiUGVyaW9kaWNhbGx5IGV4cG9zZWQgc2hvcmVzIg0KRVVOSVNhXzMgPSAiUTYxIiA6ICJQZXJpb2RpY2FsbHkgZXhwb3NlZCBzaG9yZSB3aXRoIHN0YWJsZSwgZXV0cm9waGljIHNlZGltZW50cyB3aXRoDQpwaW9uZWVyIG9yIGVwaGVtZXJhbCB2ZWdldGF0aW9uIg0KRVVOSVNhXzMgPT0gIlE2MiIgOiAiUGVyaW9kaWNhbGx5IGV4cG9zZWQgc2hvcmUgd2l0aCBzdGFibGUsIG1lc290cm9waGljIHNlZGltZW50cyB3aXRoIHBpb25lZXIgb3IgZXBoZW1lcmFsIHZlZ2V0YXRpb24iDQoNClRoaXMgY2xhc3NpZmljYXRpb24gb2YgUSArIG51bWJlcnMgaXMgbm93IGNvZXhpc3RpbmcgaW4gdGhlIGRhdGFiYXNlIHdpdGggUWEgJiBRYiAobWV0YWRhdGEpLiBIb3cgdG8gcHJvY2VlZD8NCg0KYGBge3J9DQpkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUSIpICU+JSBkaXN0aW5jdChFVU5JU2FfMikNCmBgYA0KDQoNCiMgUGxvdHMgb2YgbGV2ZWwtMiBjYXRlZ29yaWVzIHdpdGhpbiBlYWNoIGxldmVsIDEgY2F0ZWdvcnkNCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIk1BIiksIGFlcyhFVU5JU2FfMl9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDIiKSArIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIk1BIikgJT4lIGRpc3RpbmN0KEVVTklTYV8xX2Rlc2NyKSkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsIk1BX2xldmVsMi50aWZmIiksDQogICAgICAgd2lkdGg9MTQsaGVpZ2h0PTgsdW5pdHM9ImNtIixkcGk9MzAwKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJOIiksIGFlcyhFVU5JU2FfMl9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDIiKSArIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIk4iKSAlPiUgZGlzdGluY3QoRVVOSVNhXzFfZGVzY3IpKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiTl9sZXZlbDIudGlmZiIpLA0KICAgICAgIHdpZHRoPTE0LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUCIpLCBhZXMoRVVOSVNhXzJfZGVzY3IpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBsZXZlbCAyIikgKyBjb29yZF9mbGlwKCkgKw0KICBnZ3RpdGxlKGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJQIikgJT4lIGRpc3RpbmN0KEVVTklTYV8xX2Rlc2NyKSkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsIlBfbGV2ZWwyLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xNCxoZWlnaHQ9OCx1bml0cz0iY20iLGRwaT0zMDApDQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlEiKSwgYWVzKEVVTklTYV8yX2Rlc2NyKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiRVVOSVMgbGV2ZWwgMiIpICsgY29vcmRfZmxpcCgpICsNCiAgZ2d0aXRsZShkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUSIpICU+JSBkaXN0aW5jdChFVU5JU2FfMV9kZXNjcikpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJRX2xldmVsMi50aWZmIiksDQogICAgICAgd2lkdGg9MTQsaGVpZ2h0PTgsdW5pdHM9ImNtIixkcGk9MzAwKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJSIiksIGFlcyhFVU5JU2FfMl9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDIiKSArIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlIiKSAlPiUgZGlzdGluY3QoRVVOSVNhXzFfZGVzY3IpKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiUl9sZXZlbDIudGlmZiIpLA0KICAgICAgIHdpZHRoPTE0LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUyIpLCBhZXMoRVVOSVNhXzJfZGVzY3IpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBsZXZlbCAyIikgKyBjb29yZF9mbGlwKCkgKw0KICBnZ3RpdGxlKGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJTIikgJT4lIGRpc3RpbmN0KEVVTklTYV8xX2Rlc2NyKSkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsIlNfbGV2ZWwyLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xNixoZWlnaHQ9OCx1bml0cz0iY20iLGRwaT0zMDApDQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlQiKSwgYWVzKEVVTklTYV8yX2Rlc2NyKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiRVVOSVMgbGV2ZWwgMiIpICsgY29vcmRfZmxpcCgpICsNCiAgZ2d0aXRsZShkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiVCIpICU+JSBkaXN0aW5jdChFVU5JU2FfMV9kZXNjcikpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJUX2xldmVsMi50aWZmIiksDQogICAgICAgd2lkdGg9MTQsaGVpZ2h0PTgsdW5pdHM9ImNtIixkcGk9MzAwKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJVIiksIGFlcyhFVU5JU2FfMl9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDIiKSArIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlUiKSAlPiUgZGlzdGluY3QoRVVOSVNhXzFfZGVzY3IpKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiVV9sZXZlbDIudGlmZiIpLA0KICAgICAgIHdpZHRoPTE2LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiViIpLCBhZXMoRVVOSVNhXzJfZGVzY3IpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBsZXZlbCAyIikgKyBjb29yZF9mbGlwKCkgKw0KICBnZ3RpdGxlKGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJWIikgJT4lIGRpc3RpbmN0KEVVTklTYV8xX2Rlc2NyKSkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsIlZfbGV2ZWwyLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xNCxoZWlnaHQ9OCx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KIyBTYXZlIHRvIGNsZWFuIGRhdGENCg0KU2F2ZSBzby1mYXIgY2xlYW4gZGF0YWZpbGUgZm9yIHJlc3VydmV5IGRhdGFiYXNlOg0KDQpgYGB7cn0NCndyaXRlX3RzdihkYl9yZXN1cnYsaGVyZSgiZGF0YSIsICJjbGVhbiIsImRiX3Jlc3Vydl9jbGVhbi5jc3YiKSkNCmBgYA0KDQojIFNlc3Npb24gaW5mbw0KDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQoNCg0K